Compare commits

..

9 Commits

Author SHA1 Message Date
James Holderness
95cca5470e Improve the VT cursor movement implementation (#3628)
## Summary of the Pull Request

Originally there were 3 different methods for implementing VT cursor movement, and between them they still couldn't handle some of the operations correctly. This PR unifies those operations into a single method that can handle every type of cursor movement, and which fixes some of the issues with the existing implementations. In particular it fixes the `CNL` and `CPL` operations, so they're now correctly constrained by the `DECSTBM` margins.

## References

If this PR is accepted, the method added here should make it trivial to implement the `VPR` and `HPR` commands in issue #3428.

## PR Checklist
* [x] Closes #2926
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
* [ ] Requires documentation to be updated
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

The new [`AdaptDispatch::_CursorMovePosition`](d6c4f35cf6/src/terminal/adapter/adaptDispatch.cpp (L169)) method is based on the proposal I made in issue #3428 for the `VPR` and `HPR` comands. It takes three arguments: a row offset (which can be absolute or relative), a column offset (ditto), and a flag specifying whether the position should be constrained by the `DECSTBM` margins.

To make the code more readable, I've implemented the offsets using [a `struct` with some `constexpr` helper functions for the construction](d6c4f35cf6/src/terminal/adapter/adaptDispatch.hpp (L116-L125)). This lets you specify the parameters with expressions like `Offset::Absolute(col)` or `Offset::Forward(distance)` which I think makes the calling code a little easier to understand.

While implementing this new method, I noticed a couple of issues in the existing movement implementations which I thought would be good to fix at the same time.

1. When cursor movement is constrained horizontally, it should be constrained by the buffer width, and not the horizontal viewport boundaries. This is an issue I've previously corrected in other parts of the codebase, and I think the cursor movement was one of the last areas where it was still a problem.

2. A number of the commands had range and overflow checks for their parameters that were either unnecessary (testing for a condition that could never occur) or incorrect (if an operation overflows, the correct behavior is to clamp it, and not just fail). The new implementation handles legitimate overflows correctly, but doesn't check for impossible ranges.

Because of the change of behavior in point 1, I also had to update the implementations of [the `DECSC` and `CPR` commands](9cf7a9b577) to account for the column offset now being relative to the buffer and not the viewport, otherwise those operations would no longer work correctly.

## Validation Steps Performed

Because of the two changes in behavior mentioned above, there were a number of adapter tests that stopped working and needed to be updated. First off there were those that expected the column offset to be relative to the left viewport position and constrained by the viewport width. These now had to be updated to [use the full buffer width](49887a3589) as the allowed horizontal extent.

Then there were all the overflow and out-of-range tests that were testing conditions that could never occur in practice, or where the expected behavior that was tested was actually incorrect. I did spend some time trying to see if there was value in updating these tests somehow, but in the end I decided it was best to just [drop them](6e80d0de19) altogether.

For the `CNL` and `CPL` operations, there didn't appear to be any existing tests, so I added some [new screen buffer tests](d6c4f35cf6) to check that those operations now work correctly, both with and without margins.

(cherry picked from commit 2fec1787a0)
2020-01-25 17:23:02 -08:00
Dustin L. Howett (MSFT)
795fb69865 Shut down all controls under a tab before we remove it from the list (#4337)
This commit introduces a new recursive pane shutdown that will give all
controls under a tab a chance to clean up their state before beign
detached from the UI. It also reorders the call to LastTabClosed() so
that the application does not exit before the final connections are
terminated.

It also teaches TSFInputControl how to shut down to avoid a dramatic
platform bug.

Fixes #4159.
Fixes #4336.

## PR Checklist
* [x] CLA signed
* [x] I've discussed this with core contributors already.

## Validation Steps Performed
Validated through manual terminal teardown within and without the debugger, given a crazy number of panes and tabs.

(cherry picked from commit 82f302b714)
2020-01-24 14:43:26 -08:00
Mili (Yi) Zhang
90157e30d3 Fix column count issues with certain ligature. (#4081)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
This change tries to fix column size calculation when shaping return glyphs that represents multiple characters (e.g. ligature).

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? -->
## References

This should fix #696.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #xxx
* [X] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Requires documentation to be updated
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

Currently, it seems like CustomTextLayout::_CorrectGlyphRun generally assumes that glyphs and characters have a 1:1 mapping relationship - which holds true for most trivial scenarios with basic western scripts, and also many, but unfortunately not all, monospace "programming" fonts with programming ligatures.

This change makes terminal correctly processes glyphs that represents multiple characters, by properly accumulating the column counts of all these characters together (which I believe is more close to what this code originally intended to do).

There are still many issues existing in both CustomTextLayout as well as the TextBuffer, and the correct solution to them will likely demand large-scale changes, at least at the scale of #3578. I wish small changes like this can serve as a stop gap solution while we take our time to work on the long-term right thing.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Builds and runs. Manual testing confirmed that it solves #696 with both LigConsalata and Fixedsys Excelsior.

(cherry picked from commit 027f1228cb)
2020-01-24 14:43:26 -08:00
Michael Kitzan
a925ecea72 Fix crash related to unparseable/invalid media resource paths (#4194)
WT crashes when an unparseable/invalid `backgroundImage` or `icon`
resource path is provided in `profiles.json`. This PR averts the crash
by the validating and correcting resource paths as a part of the
`_ValidateSettings()` function in `CascadiaSettings`.
`_ValidateSettings()` is run on start up and any time `profiles.json` is
changed, so a user can not change a file path and avoid the validation
step.

When a bad `backgroundImage` or `icon` resource path is detected, a
warning screen will be presented.

References #4002, which identified a consistent repro for the crash.

To validate the resource, a `Windows::Foundation::Uri` object is
constructed with the path. The ctor will throw if the resource path is
invalid. Whether or not this validation method is robust enough is a
subject worth review. The correction method for when a bad resource path
is detected is to reset the `std::optional<winrt::hstring>` holding the
file path.

The text in the warning display was cribbed from the text used when an
invalid `colorScheme` is used. Whether or not the case of a bad
background image file path warrants a warning display is a subject worth
review.

Ensured the repro steps in #4002 did not trigger a crash. Additionally,
some potential backdoor paths to a crash were tested:

- Deleting the file of a validated background image file path
- Changing the actual file name of a validated background image file
  path
- Replacing the file of a validated background image file path with a
  non-image file (of the same name)
- Using a non-image file as a background image

In all the above cases WT does not crash, and instead defaults to the
background color specified in the profile's `colorScheme`. This PR does
not implement this recovery behavior (existing error catching code
does).

Closes #2329

(cherry picked from commit 77dd51af39)
2020-01-24 14:43:26 -08:00
Leonard Hecker
3b53014d90 Fixed a deadlock when printing surrogate pairs (#4150)
## Summary of the Pull Request

See [my code comment](https://github.com/microsoft/terminal/pull/4150#discussion_r364392640) below for technical details of the issue that caused #4145.

## PR Checklist
* [x] Closes #1360, Closes #4145.
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Requires documentation to be updated
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

TBH I kinda hope this project could migrate to an internal use of UTF-8 in the future. 😶

## Validation Steps Performed

Followed the "Steps to reproduce" in #4145 and ensured the "Expected behavior" happens.

(cherry picked from commit 3e6b4b57a0)
2020-01-24 14:43:26 -08:00
mcpiroman
ea690e1c09 Fix redundant CR in formatted text copy (#4190)
## Summary of the Pull Request

When `GenHTML` or `GenRTF` encountered an empty line, they assumed that `CR` is the last character of the row and wrote it, even though in general `CR` and `LF` just break the line and instead of them either `<BR>` in HTML or `\line` in RTF is written. Don't know how I missed that in #2038.

Another question is whether the `TextAndColor` structure which these methods receive and which is generated by `TextBuffer::GetTextForClipboard` should really contain `\r\n` at the end of each row. I think it'd be cleaner if it didn't esp. that afaik these last 2 characters don't have associated valid color information.

## References

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [X] Closes #4187
* [X] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed - there aren't any related tests, right?
* [ ] Requires documentation to be updated
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #4147

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Copied various terminal states and verified the generated HTML.

(cherry picked from commit 1ca29128d4)
2020-01-24 14:43:26 -08:00
Michael Niksa
0c77366b23 stab in the dark to fix x86 tests. (#4202)
## Summary of the Pull Request
Perform checking on `std::basic_string_view<T>.substr()` calls to
prevent running out of bounds and sporadic Privileged Instruction throws
during x86 tests.

## PR Checklist
* [x] Closes the x86 tests failing all over the place since #4125 for no
  apparent reason
* [x] I work here
* [x] Tests pass

## Detailed Description of the Pull Request / Additional comments
It appears that not all `std::basic_string_view<T>.substr()` calls are
created equally. I rooted around for other versions of the code in our
source tree and found several versions that were less careful about
checking the start position and the size than the one that appears when
building locally on dev machines.

My theory is that one of these older versions is deployed somewhere in
the CI. Instead of clamping down the size parameter appropriately or
throwing correctly when the position is out of bounds, I believe that
it's just creating a substring with a bad range over an
invalid/uninitialized memory region. Then when the test operates on
that, sometimes it turns out to trigger the privileged instruction
NTSTATUS error we are seeing in CI.

## Test Procedure
1. Fixed the thing
2. Ran the CI and it worked
3. Reverted everything and turned off all of the CI build except just
   the parser tests (and supporting libraries)
4. Ran CI and it failed
5. Put the fix back on top (cherry-pick)
6. It worked.
7. Ran it again.
8. It worked.
9. Turn all the rest of the CI build back on

(cherry picked from commit 4129ceb904)
2020-01-24 14:43:26 -08:00
Michael Kitzan
5ecff02a63 Add Ctrl+Backspace support (#3935)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Changes the <kbd>Ctrl+Backspace</kbd> input sequence and how it is processed by `InputStateMachineEngine`. Now <kbd>Ctrl+Backspace</kbd> deletes a whole word at a time (tested on WSL, CMD, and PS).

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? -->
## References

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #755
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed -> made minor edits to tests
* [ ] Requires documentation to be updated
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #755

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Changed the input sequence for <kbd>Ctrl+Backspace</kbd> to `\x1b\x8` so the sequence would pass through `_DoControlCharacter`. Changed `_DoControlCharacter` to process `\b` in a way which forms the correct `INPUT_RECORD`s to delete whole words.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
<kbd>Ctrl+Backspace</kbd> works 🎉

(cherry picked from commit 2b79bd0f62)
2020-01-24 14:43:26 -08:00
Dustin L. Howett (MSFT)
545c43ec0f when spawning a pty, be sure to provide & escape conhost's path (#4172)
Fixes #4061.

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
(cherry picked from commit 2712e41cad)
2020-01-24 14:43:26 -08:00
123 changed files with 706 additions and 6977 deletions

View File

@@ -77,39 +77,3 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## chromium/base/numerics
**Source**:
### License
```
Copyright 2015 The Chromium Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

View File

@@ -257,13 +257,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.LIB", "src\wincon
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.DLL", "src\winconpty\dll\winconptydll.vcxproj", "{A22EC5F6-7851-4B88-AC52-47249D437A52}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestHostApp", "src\cascadia\LocalTests_TerminalApp\TestHostApp\TestHostApp.vcxproj", "{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BDB237B6-1D1D-400F-84CC-40A58FA59C8E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "til.unit.tests", "src\til\ut_til\til.unit.tests.vcxproj", "{767268EE-174A-46FE-96F0-EEE698A1BBC9}"
EndProject
Global
@@ -1327,30 +1320,6 @@ Global
{A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|x64.Build.0 = Release|x64
{A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|x86.ActiveCfg = Release|Win32
{A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|x86.Build.0 = Release|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|Any CPU.ActiveCfg = AuditMode|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|x64.ActiveCfg = AuditMode|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|Any CPU.ActiveCfg = Debug|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|ARM64.Build.0 = Debug|ARM64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|ARM64.Deploy.0 = Debug|ARM64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x64.ActiveCfg = Debug|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x64.Build.0 = Debug|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x64.Deploy.0 = Debug|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x86.ActiveCfg = Debug|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x86.Build.0 = Debug|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x86.Deploy.0 = Debug|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|Any CPU.ActiveCfg = Release|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|ARM64.ActiveCfg = Release|ARM64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|ARM64.Build.0 = Release|ARM64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|ARM64.Deploy.0 = Release|ARM64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x64.ActiveCfg = Release|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x64.Build.0 = Release|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x64.Deploy.0 = Release|x64
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x86.ActiveCfg = Release|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x86.Build.0 = Release|Win32
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x86.Deploy.0 = Release|Win32
{767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
@@ -1421,7 +1390,7 @@ Global
{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202}
{2C2BEEF4-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{2C2BEEF4-9333-4D05-B12A-1905CBF112F9} = {59840756-302F-44DF-AA47-441A9D673202}
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{16376381-CE22-42BE-B667-C6B35007008D} = {81C352DB-1818-45B7-A284-18E259F1CC87}
{F1995847-4AE5-479A-BBAF-382E51A63532} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
@@ -1430,16 +1399,14 @@ Global
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {59840756-302F-44DF-AA47-441A9D673202}
{376FE273-6B84-4EB5-8B30-8DE9D21B022C} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{CA5CAD1A-9333-4D05-B12A-1905CBF112F9} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {59840756-302F-44DF-AA47-441A9D673202}
{48D21369-3D7B-4431-9967-24E81292CF63} = {05500DEF-2294-41E3-AF9A-24E580B82836}
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} = {59840756-302F-44DF-AA47-441A9D673202}
{B0AC39D6-7B40-49A9-8202-58549BAE1FB1} = {59840756-302F-44DF-AA47-441A9D673202}
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{A22EC5F6-7851-4B88-AC52-47249D437A52} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{BDB237B6-1D1D-400F-84CC-40A58FA59C8E} = {59840756-302F-44DF-AA47-441A9D673202}
{767268EE-174A-46FE-96F0-EEE698A1BBC9} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

View File

@@ -3,9 +3,9 @@
<!-- This file is read by XES, which we use in our Release builds. -->
<PropertyGroup Label="Version">
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2019</XesBaseYearForStoreVersion>
<VersionMajor>0</VersionMajor>
<VersionMinor>9</VersionMinor>
<VersionMinor>8</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -1,27 +0,0 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,17 +0,0 @@
### Notes for Future Maintainers
This was originally imported by @miniksa in January 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
## What should be done to update this in the future?
1. Go to chromium/chromium repository on GitHub.
2. Take the entire contents of the base/numerics directory wholesale and drop it in the base/numerics directory here.
3. Don't change anything about it.
4. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
5. Submit the pull.

View File

@@ -1,28 +0,0 @@
# Copyright (c) 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This is a dependency-free, header-only, library, and it needs to stay that
# way to facilitate pulling it into various third-party projects. So, this
# file is here to protect against accidentally introducing external
# dependencies or depending on internal implementation details.
source_set("base_numerics") {
visibility = [ "//base/*" ]
sources = [
"checked_math_impl.h",
"clamped_math_impl.h",
"safe_conversions_arm_impl.h",
"safe_conversions_impl.h",
"safe_math_arm_impl.h",
"safe_math_clang_gcc_impl.h",
"safe_math_shared_impl.h",
]
public = [
"checked_math.h",
"clamped_math.h",
"math_constants.h",
"ranges.h",
"safe_conversions.h",
"safe_math.h",
]
}

View File

@@ -1,7 +0,0 @@
# This is a dependency-free, header-only, library, and it needs to stay that
# way to facilitate pulling it into various third-party projects. So, this
# file is here to protect against accidentally introducing dependencies.
include_rules = [
"-base",
"+base/numerics",
]

View File

@@ -1,5 +0,0 @@
jschuh@chromium.org
tsepez@chromium.org
# COMPONENT: Internals

View File

@@ -1,409 +0,0 @@
# `base/numerics`
This directory contains a dependency-free, header-only library of templates
providing well-defined semantics for safely and performantly handling a variety
of numeric operations, including most common arithmetic operations and
conversions.
The public API is broken out into the following header files:
* `checked_math.h` contains the `CheckedNumeric` template class and helper
functions for performing arithmetic and conversion operations that detect
errors and boundary conditions (e.g. overflow, truncation, etc.).
* `clamped_math.h` contains the `ClampedNumeric` template class and
helper functions for performing fast, clamped (i.e. [non-sticky](#notsticky)
saturating) arithmetic operations and conversions.
* `safe_conversions.h` contains the `StrictNumeric` template class and
a collection of custom casting templates and helper functions for safely
converting between a range of numeric types.
* `safe_math.h` includes all of the previously mentioned headers.
*** aside
**Note:** The `Numeric` template types implicitly convert from C numeric types
and `Numeric` templates that are convertable to an underlying C numeric type.
The conversion priority for `Numeric` type coercions is:
* `StrictNumeric` coerces to `ClampedNumeric` and `CheckedNumeric`
* `ClampedNumeric` coerces to `CheckedNumeric`
***
[TOC]
## Common patterns and use-cases
The following covers the preferred style for the most common uses of this
library. Please don't cargo-cult from anywhere else. 😉
### Performing checked arithmetic type conversions
The `checked_cast` template converts between arbitrary arithmetic types, and is
used for cases where a conversion failure should result in program termination:
```cpp
// Crash if signed_value is out of range for buff_size.
size_t buff_size = checked_cast<size_t>(signed_value);
```
### Performing saturated (clamped) arithmetic type conversions
The `saturated_cast` template converts between arbitrary arithmetic types, and
is used in cases where an out-of-bounds source value should be saturated to the
corresponding maximum or minimum of the destination type:
```cpp
// Convert from float with saturation to INT_MAX, INT_MIN, or 0 for NaN.
int int_value = saturated_cast<int>(floating_point_value);
```
### Enforcing arithmetic type conversions at compile-time
The `strict_cast` emits code that is identical to `static_cast`. However,
provides static checks that will cause a compilation failure if the
destination type cannot represent the full range of the source type:
```cpp
// Throw a compiler error if byte_value is changed to an out-of-range-type.
int int_value = strict_cast<int>(byte_value);
```
You can also enforce these compile-time restrictions on function parameters by
using the `StrictNumeric` template:
```cpp
// Throw a compiler error if the size argument cannot be represented by a
// size_t (e.g. passing an int will fail to compile).
bool AllocateBuffer(void** buffer, StrictCast<size_t> size);
```
### Comparing values between arbitrary arithmetic types
Both the `StrictNumeric` and `ClampedNumeric` types provide well defined
comparisons between arbitrary arithmetic types. This allows you to perform
comparisons that are not legal or would trigger compiler warnings or errors
under the normal arithmetic promotion rules:
```cpp
bool foo(unsigned value, int upper_bound) {
// Converting to StrictNumeric allows this comparison to work correctly.
if (MakeStrictNum(value) >= upper_bound)
return false;
```
*** note
**Warning:** Do not perform manual conversions using the comparison operators.
Instead, use the cast templates described in the previous sections, or the
constexpr template functions `IsValueInRangeForNumericType` and
`IsTypeInRangeForNumericType`, as these templates properly handle the full range
of corner cases and employ various optimizations.
***
### Calculating a buffer size (checked arithmetic)
When making exact calculations—such as for buffer lengths—it's often necessary
to know when those calculations trigger an overflow, undefined behavior, or
other boundary conditions. The `CheckedNumeric` template does this by storing
a bit determining whether or not some arithmetic operation has occured that
would put the variable in an "invalid" state. Attempting to extract the value
from a variable in an invalid state will trigger a check/trap condition, that
by default will result in process termination.
Here's an example of a buffer calculation using a `CheckedNumeric` type (note:
the AssignIfValid method will trigger a compile error if the result is ignored).
```cpp
// Calculate the buffer size and detect if an overflow occurs.
size_t size;
if (!CheckAdd(kHeaderSize, CheckMul(count, kItemSize)).AssignIfValid(&size)) {
// Handle an overflow error...
}
```
### Calculating clamped coordinates (non-sticky saturating arithmetic)
Certain classes of calculations—such as coordinate calculations—require
well-defined semantics that always produce a valid result on boundary
conditions. The `ClampedNumeric` template addresses this by providing
performant, non-sticky saturating arithmetic operations.
Here's an example of using a `ClampedNumeric` to calculate an operation
insetting a rectangle.
```cpp
// Use clamped arithmetic since inset calculations might overflow.
void Rect::Inset(int left, int top, int right, int bottom) {
origin_ += Vector2d(left, top);
set_width(ClampSub(width(), ClampAdd(left, right)));
set_height(ClampSub(height(), ClampAdd(top, bottom)));
}
```
*** note
<a name="notsticky"></a>
The `ClampedNumeric` type is not "sticky", which means the saturation is not
retained across individual operations. As such, one arithmetic operation may
result in a saturated value, while the next operation may then "desaturate"
the value. Here's an example:
```cpp
ClampedNumeric<int> value = INT_MAX;
++value; // value is still INT_MAX, due to saturation.
--value; // value is now (INT_MAX - 1), because saturation is not sticky.
```
***
## Conversion functions and StrictNumeric<> in safe_conversions.h
This header includes a collection of helper `constexpr` templates for safely
performing a range of conversions, assignments, and tests.
### Safe casting templates
* `as_signed()` - Returns the supplied integral value as a signed type of
the same width.
* `as_unsigned()` - Returns the supplied integral value as an unsigned type
of the same width.
* `checked_cast<>()` - Analogous to `static_cast<>` for numeric types, except
that by default it will trigger a crash on an out-of-bounds conversion (e.g.
overflow, underflow, NaN to integral) or a compile error if the conversion
error can be detected at compile time. The crash handler can be overridden
to perform a behavior other than crashing.
* `saturated_cast<>()` - Analogous to `static_cast` for numeric types, except
that it returns a saturated result when the specified numeric conversion
would otherwise overflow or underflow. An NaN source returns 0 by
default, but can be overridden to return a different result.
* `strict_cast<>()` - Analogous to `static_cast` for numeric types, except
this causes a compile failure if the destination type is not large
enough to contain any value in the source type. It performs no runtime
checking and thus introduces no runtime overhead.
### Other helper and conversion functions
* `IsValueInRangeForNumericType<>()` - A convenience function that returns
true if the type supplied as the template parameter can represent the value
passed as an argument to the function.
* `IsTypeInRangeForNumericType<>()` - A convenience function that evaluates
entirely at compile-time and returns true if the destination type (first
template parameter) can represent the full range of the source type
(second template parameter).
* `IsValueNegative()` - A convenience function that will accept any
arithmetic type as an argument and will return whether the value is less
than zero. Unsigned types always return false.
* `SafeUnsignedAbs()` - Returns the absolute value of the supplied integer
parameter as an unsigned result (thus avoiding an overflow if the value
is the signed, two's complement minimum).
### StrictNumeric<>
`StrictNumeric<>` is a wrapper type that performs assignments and copies via
the `strict_cast` template, and can perform valid arithmetic comparisons
across any range of arithmetic types. `StrictNumeric` is the return type for
values extracted from a `CheckedNumeric` class instance. The raw numeric value
is extracted via `static_cast` to the underlying type or any type with
sufficient range to represent the underlying type.
* `MakeStrictNum()` - Creates a new `StrictNumeric` from the underlying type
of the supplied arithmetic or StrictNumeric type.
* `SizeT` - Alias for `StrictNumeric<size_t>`.
## CheckedNumeric<> in checked_math.h
`CheckedNumeric<>` implements all the logic and operators for detecting integer
boundary conditions such as overflow, underflow, and invalid conversions.
The `CheckedNumeric` type implicitly converts from floating point and integer
data types, and contains overloads for basic arithmetic operations (i.e.: `+`,
`-`, `*`, `/` for all types and `%`, `<<`, `>>`, `&`, `|`, `^` for integers).
However, *the [variadic template functions
](#CheckedNumeric_in-checked_math_h-Non_member-helper-functions)
are the prefered API,* as they remove type ambiguities and help prevent a number
of common errors. The variadic functions can also be more performant, as they
eliminate redundant expressions that are unavoidable with the with the operator
overloads. (Ideally the compiler should optimize those away, but better to avoid
them in the first place.)
Type promotions are a slightly modified version of the [standard C/C++ numeric
promotions
](http://en.cppreference.com/w/cpp/language/implicit_conversion#Numeric_promotions)
with the two differences being that *there is no default promotion to int*
and *bitwise logical operations always return an unsigned of the wider type.*
### Members
The unary negation, increment, and decrement operators are supported, along
with the following unary arithmetic methods, which return a new
`CheckedNumeric` as a result of the operation:
* `Abs()` - Absolute value.
* `UnsignedAbs()` - Absolute value as an equal-width unsigned underlying type
(valid for only integral types).
* `Max()` - Returns whichever is greater of the current instance or argument.
The underlying return type is whichever has the greatest magnitude.
* `Min()` - Returns whichever is lowest of the current instance or argument.
The underlying return type is whichever has can represent the lowest
number in the smallest width (e.g. int8_t over unsigned, int over
int8_t, and float over int).
The following are for converting `CheckedNumeric` instances:
* `type` - The underlying numeric type.
* `AssignIfValid()` - Assigns the underlying value to the supplied
destination pointer if the value is currently valid and within the
range supported by the destination type. Returns true on success.
* `Cast<>()` - Instance method returning a `CheckedNumeric` derived from
casting the current instance to a `CheckedNumeric` of the supplied
destination type.
*** aside
The following member functions return a `StrictNumeric`, which is valid for
comparison and assignment operations, but will trigger a compile failure on
attempts to assign to a type of insufficient range. The underlying value can
be extracted by an explicit `static_cast` to the underlying type or any type
with sufficient range to represent the underlying type.
***
* `IsValid()` - Returns true if the underlying numeric value is valid (i.e.
has not wrapped or saturated and is not the result of an invalid
conversion).
* `ValueOrDie()` - Returns the underlying value. If the state is not valid
this call will trigger a crash by default (but may be overridden by
supplying an alternate handler to the template).
* `ValueOrDefault()` - Returns the current value, or the supplied default if
the state is not valid (but will not crash).
**Comparison operators are explicitly not provided** for `CheckedNumeric`
types because they could result in a crash if the type is not in a valid state.
Patterns like the following should be used instead:
```cpp
// Either input or padding (or both) may be arbitrary sizes.
size_t buff_size;
if (!CheckAdd(input, padding, kHeaderLength).AssignIfValid(&buff_size) ||
buff_size >= kMaxBuffer) {
// Handle an error...
} else {
// Do stuff on success...
}
```
### Non-member helper functions
The following variadic convenience functions, which accept standard arithmetic
or `CheckedNumeric` types, perform arithmetic operations, and return a
`CheckedNumeric` result. The supported functions are:
* `CheckAdd()` - Addition.
* `CheckSub()` - Subtraction.
* `CheckMul()` - Multiplication.
* `CheckDiv()` - Division.
* `CheckMod()` - Modulus (integer only).
* `CheckLsh()` - Left integer shift (integer only).
* `CheckRsh()` - Right integer shift (integer only).
* `CheckAnd()` - Bitwise AND (integer only with unsigned result).
* `CheckOr()` - Bitwise OR (integer only with unsigned result).
* `CheckXor()` - Bitwise XOR (integer only with unsigned result).
* `CheckMax()` - Maximum of supplied arguments.
* `CheckMin()` - Minimum of supplied arguments.
The following wrapper functions can be used to avoid the template
disambiguator syntax when converting a destination type.
* `IsValidForType<>()` in place of: `a.template IsValid<>()`
* `ValueOrDieForType<>()` in place of: `a.template ValueOrDie<>()`
* `ValueOrDefaultForType<>()` in place of: `a.template ValueOrDefault<>()`
The following general utility methods is are useful for converting from
arithmetic types to `CheckedNumeric` types:
* `MakeCheckedNum()` - Creates a new `CheckedNumeric` from the underlying type
of the supplied arithmetic or directly convertible type.
## ClampedNumeric<> in clamped_math.h
`ClampedNumeric<>` implements all the logic and operators for clamped
(non-sticky saturating) arithmetic operations and conversions. The
`ClampedNumeric` type implicitly converts back and forth between floating point
and integer data types, saturating on assignment as appropriate. It contains
overloads for basic arithmetic operations (i.e.: `+`, `-`, `*`, `/` for
all types and `%`, `<<`, `>>`, `&`, `|`, `^` for integers) along with comparison
operators for arithmetic types of any size. However, *the [variadic template
functions
](#ClampedNumeric_in-clamped_math_h-Non_member-helper-functions)
are the prefered API,* as they remove type ambiguities and help prevent
a number of common errors. The variadic functions can also be more performant,
as they eliminate redundant expressions that are unavoidable with the operator
overloads. (Ideally the compiler should optimize those away, but better to avoid
them in the first place.)
Type promotions are a slightly modified version of the [standard C/C++ numeric
promotions
](http://en.cppreference.com/w/cpp/language/implicit_conversion#Numeric_promotions)
with the two differences being that *there is no default promotion to int*
and *bitwise logical operations always return an unsigned of the wider type.*
*** aside
Most arithmetic operations saturate normally, to the numeric limit in the
direction of the sign. The potentially unusual cases are:
* **Division:** Division by zero returns the saturated limit in the direction
of sign of the dividend (first argument). The one exception is 0/0, which
returns zero (although logically is NaN).
* **Modulus:** Division by zero returns the dividend (first argument).
* **Left shift:** Non-zero values saturate in the direction of the signed
limit (max/min), even for shifts larger than the bit width. 0 shifted any
amount results in 0.
* **Right shift:** Negative values saturate to -1. Positive or 0 saturates
to 0. (Effectively just an unbounded arithmetic-right-shift.)
* **Bitwise operations:** No saturation; bit pattern is identical to
non-saturated bitwise operations.
***
### Members
The unary negation, increment, and decrement operators are supported, along
with the following unary arithmetic methods, which return a new
`ClampedNumeric` as a result of the operation:
* `Abs()` - Absolute value.
* `UnsignedAbs()` - Absolute value as an equal-width unsigned underlying type
(valid for only integral types).
* `Max()` - Returns whichever is greater of the current instance or argument.
The underlying return type is whichever has the greatest magnitude.
* `Min()` - Returns whichever is lowest of the current instance or argument.
The underlying return type is whichever has can represent the lowest
number in the smallest width (e.g. int8_t over unsigned, int over
int8_t, and float over int).
The following are for converting `ClampedNumeric` instances:
* `type` - The underlying numeric type.
* `RawValue()` - Returns the raw value as the underlying arithmetic type. This
is useful when e.g. assigning to an auto type or passing as a deduced
template parameter.
* `Cast<>()` - Instance method returning a `ClampedNumeric` derived from
casting the current instance to a `ClampedNumeric` of the supplied
destination type.
### Non-member helper functions
The following variadic convenience functions, which accept standard arithmetic
or `ClampedNumeric` types, perform arithmetic operations, and return a
`ClampedNumeric` result. The supported functions are:
* `ClampAdd()` - Addition.
* `ClampSub()` - Subtraction.
* `ClampMul()` - Multiplication.
* `ClampDiv()` - Division.
* `ClampMod()` - Modulus (integer only).
* `ClampLsh()` - Left integer shift (integer only).
* `ClampRsh()` - Right integer shift (integer only).
* `ClampAnd()` - Bitwise AND (integer only with unsigned result).
* `ClampOr()` - Bitwise OR (integer only with unsigned result).
* `ClampXor()` - Bitwise XOR (integer only with unsigned result).
* `ClampMax()` - Maximum of supplied arguments.
* `ClampMin()` - Minimum of supplied arguments.
The following is a general utility method that is useful for converting
to a `ClampedNumeric` type:
* `MakeClampedNum()` - Creates a new `ClampedNumeric` from the underlying type
of the supplied arithmetic or directly convertible type.

View File

@@ -1,393 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CHECKED_MATH_H_
#define BASE_NUMERICS_CHECKED_MATH_H_
#include <stddef.h>
#include <limits>
#include <type_traits>
#include "base/numerics/checked_math_impl.h"
namespace base {
namespace internal {
template <typename T>
class CheckedNumeric {
static_assert(std::is_arithmetic<T>::value,
"CheckedNumeric<T>: T must be a numeric type.");
public:
using type = T;
constexpr CheckedNumeric() = default;
// Copy constructor.
template <typename Src>
constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
: state_(rhs.state_.value(), rhs.IsValid()) {}
template <typename Src>
friend class CheckedNumeric;
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to CheckedNumerics to make them easier to use.
template <typename Src>
constexpr CheckedNumeric(Src value) // NOLINT(runtime/explicit)
: state_(value) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
}
// This is not an explicit constructor because we want a seamless conversion
// from StrictNumeric types.
template <typename Src>
constexpr CheckedNumeric(
StrictNumeric<Src> value) // NOLINT(runtime/explicit)
: state_(static_cast<Src>(value)) {}
// IsValid() - The public API to test if a CheckedNumeric is currently valid.
// A range checked destination type can be supplied using the Dst template
// parameter.
template <typename Dst = T>
constexpr bool IsValid() const {
return state_.is_valid() &&
IsValueInRangeForNumericType<Dst>(state_.value());
}
// AssignIfValid(Dst) - Assigns the underlying value if it is currently valid
// and is within the range supported by the destination type. Returns true if
// successful and false otherwise.
template <typename Dst>
#if defined(__clang__) || defined(__GNUC__)
__attribute__((warn_unused_result))
#elif defined(_MSC_VER)
_Check_return_
#endif
constexpr bool
AssignIfValid(Dst* result) const {
return BASE_NUMERICS_LIKELY(IsValid<Dst>())
? ((*result = static_cast<Dst>(state_.value())), true)
: false;
}
// ValueOrDie() - The primary accessor for the underlying value. If the
// current state is not valid it will CHECK and crash.
// A range checked destination type can be supplied using the Dst template
// parameter, which will trigger a CHECK if the value is not in bounds for
// the destination.
// The CHECK behavior can be overridden by supplying a handler as a
// template parameter, for test code, etc. However, the handler cannot access
// the underlying value, and it is not available through other means.
template <typename Dst = T, class CheckHandler = CheckOnFailure>
constexpr StrictNumeric<Dst> ValueOrDie() const {
return BASE_NUMERICS_LIKELY(IsValid<Dst>())
? static_cast<Dst>(state_.value())
: CheckHandler::template HandleFailure<Dst>();
}
// ValueOrDefault(T default_value) - A convenience method that returns the
// current value if the state is valid, and the supplied default_value for
// any other state.
// A range checked destination type can be supplied using the Dst template
// parameter. WARNING: This function may fail to compile or CHECK at runtime
// if the supplied default_value is not within range of the destination type.
template <typename Dst = T, typename Src>
constexpr StrictNumeric<Dst> ValueOrDefault(const Src default_value) const {
return BASE_NUMERICS_LIKELY(IsValid<Dst>())
? static_cast<Dst>(state_.value())
: checked_cast<Dst>(default_value);
}
// Returns a checked numeric of the specified type, cast from the current
// CheckedNumeric. If the current state is invalid or the destination cannot
// represent the result then the returned CheckedNumeric will be invalid.
template <typename Dst>
constexpr CheckedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
return *this;
}
// This friend method is available solely for providing more detailed logging
// in the the tests. Do not implement it in production code, because the
// underlying values may change at any time.
template <typename U>
friend U GetNumericValueForTest(const CheckedNumeric<U>& src);
// Prototypes for the supported arithmetic operator overloads.
template <typename Src>
constexpr CheckedNumeric& operator+=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator-=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator*=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator/=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator%=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator<<=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator>>=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator&=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator|=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator^=(const Src rhs);
constexpr CheckedNumeric operator-() const {
// The negation of two's complement int min is int min, so we simply
// check for that in the constexpr case.
// We use an optimized code path for a known run-time variable.
return MustTreatAsConstexpr(state_.value()) || !std::is_signed<T>::value ||
std::is_floating_point<T>::value
? CheckedNumeric<T>(
NegateWrapper(state_.value()),
IsValid() && (!std::is_signed<T>::value ||
std::is_floating_point<T>::value ||
NegateWrapper(state_.value()) !=
std::numeric_limits<T>::lowest()))
: FastRuntimeNegate();
}
constexpr CheckedNumeric operator~() const {
return CheckedNumeric<decltype(InvertWrapper(T()))>(
InvertWrapper(state_.value()), IsValid());
}
constexpr CheckedNumeric Abs() const {
return !IsValueNegative(state_.value()) ? *this : -*this;
}
template <typename U>
constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
const U rhs) const {
using R = typename UnderlyingType<U>::type;
using result_type = typename MathWrapper<CheckedMaxOp, T, U>::type;
// TODO(jschuh): This can be converted to the MathOp version and remain
// constexpr once we have C++14 support.
return CheckedNumeric<result_type>(
static_cast<result_type>(
IsGreater<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
? state_.value()
: Wrapper<U>::value(rhs)),
state_.is_valid() && Wrapper<U>::is_valid(rhs));
}
template <typename U>
constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
const U rhs) const {
using R = typename UnderlyingType<U>::type;
using result_type = typename MathWrapper<CheckedMinOp, T, U>::type;
// TODO(jschuh): This can be converted to the MathOp version and remain
// constexpr once we have C++14 support.
return CheckedNumeric<result_type>(
static_cast<result_type>(
IsLess<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
? state_.value()
: Wrapper<U>::value(rhs)),
state_.is_valid() && Wrapper<U>::is_valid(rhs));
}
// This function is available only for integral types. It returns an unsigned
// integer of the same width as the source type, containing the absolute value
// of the source, and properly handling signed min.
constexpr CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>
UnsignedAbs() const {
return CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>(
SafeUnsignedAbs(state_.value()), state_.is_valid());
}
constexpr CheckedNumeric& operator++() {
*this += 1;
return *this;
}
constexpr CheckedNumeric operator++(int) {
CheckedNumeric value = *this;
*this += 1;
return value;
}
constexpr CheckedNumeric& operator--() {
*this -= 1;
return *this;
}
constexpr CheckedNumeric operator--(int) {
CheckedNumeric value = *this;
*this -= 1;
return value;
}
// These perform the actual math operations on the CheckedNumerics.
// Binary arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
T result = 0;
bool is_valid =
Wrapper<L>::is_valid(lhs) && Wrapper<R>::is_valid(rhs) &&
Math::Do(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs), &result);
return CheckedNumeric<T>(result, is_valid);
}
// Assignment arithmetic operations.
template <template <typename, typename, typename> class M, typename R>
constexpr CheckedNumeric& MathOp(const R rhs) {
using Math = typename MathWrapper<M, T, R>::math;
T result = 0; // Using T as the destination saves a range check.
bool is_valid = state_.is_valid() && Wrapper<R>::is_valid(rhs) &&
Math::Do(state_.value(), Wrapper<R>::value(rhs), &result);
*this = CheckedNumeric<T>(result, is_valid);
return *this;
}
private:
CheckedNumericState<T> state_;
CheckedNumeric FastRuntimeNegate() const {
T result;
bool success = CheckedSubOp<T, T>::Do(T(0), state_.value(), &result);
return CheckedNumeric<T>(result, IsValid() && success);
}
template <typename Src>
constexpr CheckedNumeric(Src value, bool is_valid)
: state_(value, is_valid) {}
// These wrappers allow us to handle state the same way for both
// CheckedNumeric and POD arithmetic types.
template <typename Src>
struct Wrapper {
static constexpr bool is_valid(Src) { return true; }
static constexpr Src value(Src value) { return value; }
};
template <typename Src>
struct Wrapper<CheckedNumeric<Src>> {
static constexpr bool is_valid(const CheckedNumeric<Src> v) {
return v.IsValid();
}
static constexpr Src value(const CheckedNumeric<Src> v) {
return v.state_.value();
}
};
template <typename Src>
struct Wrapper<StrictNumeric<Src>> {
static constexpr bool is_valid(const StrictNumeric<Src>) { return true; }
static constexpr Src value(const StrictNumeric<Src> v) {
return static_cast<Src>(v);
}
};
};
// Convenience functions to avoid the ugly template disambiguator syntax.
template <typename Dst, typename Src>
constexpr bool IsValidForType(const CheckedNumeric<Src> value) {
return value.template IsValid<Dst>();
}
template <typename Dst, typename Src>
constexpr StrictNumeric<Dst> ValueOrDieForType(
const CheckedNumeric<Src> value) {
return value.template ValueOrDie<Dst>();
}
template <typename Dst, typename Src, typename Default>
constexpr StrictNumeric<Dst> ValueOrDefaultForType(
const CheckedNumeric<Src> value,
const Default default_value) {
return value.template ValueOrDefault<Dst>(default_value);
}
// Convience wrapper to return a new CheckedNumeric from the provided arithmetic
// or CheckedNumericType.
template <typename T>
constexpr CheckedNumeric<typename UnderlyingType<T>::type> MakeCheckedNum(
const T value) {
return value;
}
// These implement the variadic wrapper for the math operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
constexpr CheckedNumeric<typename MathWrapper<M, L, R>::type> CheckMathOp(
const L lhs,
const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return CheckedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
rhs);
}
// General purpose wrapper template for arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
constexpr CheckedNumeric<typename ResultType<M, L, R, Args...>::type>
CheckMathOp(const L lhs, const R rhs, const Args... args) {
return CheckMathOp<M>(CheckMathOp<M>(lhs, rhs), args...);
}
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Add, +, +=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Sub, -, -=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mul, *, *=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Div, /, /=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mod, %, %=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Lsh, <<, <<=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Rsh, >>, >>=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, And, &, &=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Or, |, |=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Xor, ^, ^=)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Max)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Min)
// These are some extra StrictNumeric operators to support simple pointer
// arithmetic with our result types. Since wrapping on a pointer is always
// bad, we trigger the CHECK condition here.
template <typename L, typename R>
L* operator+(L* lhs, const StrictNumeric<R> rhs) {
uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>();
return reinterpret_cast<L*>(result);
}
template <typename L, typename R>
L* operator-(L* lhs, const StrictNumeric<R> rhs) {
uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>();
return reinterpret_cast<L*>(result);
}
} // namespace internal
using internal::CheckedNumeric;
using internal::IsValidForType;
using internal::ValueOrDieForType;
using internal::ValueOrDefaultForType;
using internal::MakeCheckedNum;
using internal::CheckMax;
using internal::CheckMin;
using internal::CheckAdd;
using internal::CheckSub;
using internal::CheckMul;
using internal::CheckDiv;
using internal::CheckMod;
using internal::CheckLsh;
using internal::CheckRsh;
using internal::CheckAnd;
using internal::CheckOr;
using internal::CheckXor;
} // namespace base
#endif // BASE_NUMERICS_CHECKED_MATH_H_

View File

@@ -1,567 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CHECKED_MATH_IMPL_H_
#define BASE_NUMERICS_CHECKED_MATH_IMPL_H_
#include <stddef.h>
#include <stdint.h>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math_shared_impl.h"
namespace base {
namespace internal {
template <typename T>
constexpr bool CheckedAddImpl(T x, T y, T* result) {
static_assert(std::is_integral<T>::value, "Type must be integral");
// Since the value of x+y is undefined if we have a signed type, we compute
// it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type;
UnsignedDst ux = static_cast<UnsignedDst>(x);
UnsignedDst uy = static_cast<UnsignedDst>(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
*result = static_cast<T>(uresult);
// Addition is valid if the sign of (x + y) is equal to either that of x or
// that of y.
return (std::is_signed<T>::value)
? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0
: uresult >= uy; // Unsigned is either valid or underflow.
}
template <typename T, typename U, class Enable = void>
struct CheckedAddOp {};
template <typename T, typename U>
struct CheckedAddOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (CheckedAddFastOp<T, U>::is_supported)
return CheckedAddFastOp<T, U>::Do(x, y, result);
// Double the underlying type up to a full machine word.
using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
using Promotion =
typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
IntegerBitsPlusSign<intptr_t>::value),
typename BigEnoughPromotion<T, U>::type,
FastPromotion>::type;
// Fail if either operand is out of range for the promoted type.
// TODO(jschuh): This could be made to work for a broader range of values.
if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y))) {
return false;
}
Promotion presult = {};
bool is_valid = true;
if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
} else {
is_valid = CheckedAddImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult);
}
*result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T>
constexpr bool CheckedSubImpl(T x, T y, T* result) {
static_assert(std::is_integral<T>::value, "Type must be integral");
// Since the value of x+y is undefined if we have a signed type, we compute
// it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type;
UnsignedDst ux = static_cast<UnsignedDst>(x);
UnsignedDst uy = static_cast<UnsignedDst>(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
*result = static_cast<T>(uresult);
// Subtraction is valid if either x and y have same sign, or (x-y) and x have
// the same sign.
return (std::is_signed<T>::value)
? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0
: x >= y;
}
template <typename T, typename U, class Enable = void>
struct CheckedSubOp {};
template <typename T, typename U>
struct CheckedSubOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (CheckedSubFastOp<T, U>::is_supported)
return CheckedSubFastOp<T, U>::Do(x, y, result);
// Double the underlying type up to a full machine word.
using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
using Promotion =
typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
IntegerBitsPlusSign<intptr_t>::value),
typename BigEnoughPromotion<T, U>::type,
FastPromotion>::type;
// Fail if either operand is out of range for the promoted type.
// TODO(jschuh): This could be made to work for a broader range of values.
if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y))) {
return false;
}
Promotion presult = {};
bool is_valid = true;
if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
} else {
is_valid = CheckedSubImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult);
}
*result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T>
constexpr bool CheckedMulImpl(T x, T y, T* result) {
static_assert(std::is_integral<T>::value, "Type must be integral");
// Since the value of x*y is potentially undefined if we have a signed type,
// we compute it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type;
const UnsignedDst ux = SafeUnsignedAbs(x);
const UnsignedDst uy = SafeUnsignedAbs(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
const bool is_negative =
std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
*result = is_negative ? 0 - uresult : uresult;
// We have a fast out for unsigned identity or zero on the second operand.
// After that it's an unsigned overflow check on the absolute value, with
// a +1 bound for a negative result.
return uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) ||
ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy;
}
template <typename T, typename U, class Enable = void>
struct CheckedMulOp {};
template <typename T, typename U>
struct CheckedMulOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (CheckedMulFastOp<T, U>::is_supported)
return CheckedMulFastOp<T, U>::Do(x, y, result);
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
// Verify the destination type can hold the result (always true for 0).
if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y)) &&
x && y)) {
return false;
}
Promotion presult = {};
bool is_valid = true;
if (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
// The fast op may be available with the promoted type.
is_valid = CheckedMulFastOp<Promotion, Promotion>::Do(x, y, &presult);
} else if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
} else {
is_valid = CheckedMulImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult);
}
*result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult);
}
};
// Division just requires a check for a zero denominator or an invalid negation
// on signed min/-1.
template <typename T, typename U, class Enable = void>
struct CheckedDivOp {};
template <typename T, typename U>
struct CheckedDivOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
if (BASE_NUMERICS_UNLIKELY(!y))
return false;
// The overflow check can be compiled away if we don't have the exact
// combination of types needed to trigger this case.
using Promotion = typename BigEnoughPromotion<T, U>::type;
if (BASE_NUMERICS_UNLIKELY(
(std::is_signed<T>::value && std::is_signed<U>::value &&
IsTypeInRangeForNumericType<T, Promotion>::value &&
static_cast<Promotion>(x) ==
std::numeric_limits<Promotion>::lowest() &&
y == static_cast<U>(-1)))) {
return false;
}
// This branch always compiles away if the above branch wasn't removed.
if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y)) &&
x)) {
return false;
}
Promotion presult = Promotion(x) / Promotion(y);
*result = static_cast<V>(presult);
return IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedModOp {};
template <typename T, typename U>
struct CheckedModOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
using Promotion = typename BigEnoughPromotion<T, U>::type;
if (BASE_NUMERICS_LIKELY(y)) {
Promotion presult = static_cast<Promotion>(x) % static_cast<Promotion>(y);
*result = static_cast<Promotion>(presult);
return IsValueInRangeForNumericType<V>(presult);
}
return false;
}
};
template <typename T, typename U, class Enable = void>
struct CheckedLshOp {};
// Left shift. Shifts less than 0 or greater than or equal to the number
// of bits in the promoted type are undefined. Shifts of negative values
// are undefined. Otherwise it is defined when the result fits.
template <typename T, typename U>
struct CheckedLshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V>
static constexpr bool Do(T x, U shift, V* result) {
// Disallow negative numbers and verify the shift is in bounds.
if (BASE_NUMERICS_LIKELY(!IsValueNegative(x) &&
as_unsigned(shift) <
as_unsigned(std::numeric_limits<T>::digits))) {
// Shift as unsigned to avoid undefined behavior.
*result = static_cast<V>(as_unsigned(x) << shift);
// If the shift can be reversed, we know it was valid.
return *result >> shift == x;
}
// Handle the legal corner-case of a full-width signed shift of zero.
return std::is_signed<T>::value && !x &&
as_unsigned(shift) == as_unsigned(std::numeric_limits<T>::digits);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedRshOp {};
// Right shift. Shifts less than 0 or greater than or equal to the number
// of bits in the promoted type are undefined. Otherwise, it is always defined,
// but a right shift of a negative value is implementation-dependent.
template <typename T, typename U>
struct CheckedRshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V>
static bool Do(T x, U shift, V* result) {
// Use the type conversion push negative values out of range.
if (BASE_NUMERICS_LIKELY(as_unsigned(shift) <
IntegerBitsPlusSign<T>::value)) {
T tmp = x >> shift;
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
return false;
}
};
template <typename T, typename U, class Enable = void>
struct CheckedAndOp {};
// For simplicity we support only unsigned integer results.
template <typename T, typename U>
struct CheckedAndOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) & static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedOrOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
struct CheckedOrOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) | static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedXorOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
struct CheckedXorOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) ^ static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
// Max doesn't really need to be implemented this way because it can't fail,
// but it makes the code much cleaner to use the MathOp wrappers.
template <typename T, typename U, class Enable = void>
struct CheckedMaxOp {};
template <typename T, typename U>
struct CheckedMaxOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x)
: static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
// Min doesn't really need to be implemented this way because it can't fail,
// but it makes the code much cleaner to use the MathOp wrappers.
template <typename T, typename U, class Enable = void>
struct CheckedMinOp {};
template <typename T, typename U>
struct CheckedMinOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename LowestValuePromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x)
: static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
// This is just boilerplate that wraps the standard floating point arithmetic.
// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
template <typename T, typename U> \
struct Checked##NAME##Op< \
T, U, \
typename std::enable_if<std::is_floating_point<T>::value || \
std::is_floating_point<U>::value>::type> { \
using result_type = typename MaxExponentPromotion<T, U>::type; \
template <typename V> \
static constexpr bool Do(T x, U y, V* result) { \
using Promotion = typename MaxExponentPromotion<T, U>::type; \
Promotion presult = x OP y; \
*result = static_cast<V>(presult); \
return IsValueInRangeForNumericType<V>(presult); \
} \
};
BASE_FLOAT_ARITHMETIC_OPS(Add, +)
BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
BASE_FLOAT_ARITHMETIC_OPS(Div, /)
#undef BASE_FLOAT_ARITHMETIC_OPS
// Floats carry around their validity state with them, but integers do not. So,
// we wrap the underlying value in a specialization in order to hide that detail
// and expose an interface via accessors.
enum NumericRepresentation {
NUMERIC_INTEGER,
NUMERIC_FLOATING,
NUMERIC_UNKNOWN
};
template <typename NumericType>
struct GetNumericRepresentation {
static const NumericRepresentation value =
std::is_integral<NumericType>::value
? NUMERIC_INTEGER
: (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
: NUMERIC_UNKNOWN);
};
template <typename T,
NumericRepresentation type = GetNumericRepresentation<T>::value>
class CheckedNumericState {};
// Integrals require quite a bit of additional housekeeping to manage state.
template <typename T>
class CheckedNumericState<T, NUMERIC_INTEGER> {
private:
// is_valid_ precedes value_ because member intializers in the constructors
// are evaluated in field order, and is_valid_ must be read when initializing
// value_.
bool is_valid_;
T value_;
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrZero(const Src value,
const bool is_valid) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return (std::is_integral<SrcType>::value || is_valid)
? static_cast<T>(value)
: static_cast<T>(0);
}
public:
template <typename Src, NumericRepresentation type>
friend class CheckedNumericState;
constexpr CheckedNumericState() : is_valid_(true), value_(0) {}
template <typename Src>
constexpr CheckedNumericState(Src value, bool is_valid)
: is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
value_(WellDefinedConversionOrZero(value, is_valid_)) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
}
// Copy constructor.
template <typename Src>
constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
: is_valid_(rhs.IsValid()),
value_(WellDefinedConversionOrZero(rhs.value(), is_valid_)) {}
template <typename Src>
constexpr explicit CheckedNumericState(Src value)
: is_valid_(IsValueInRangeForNumericType<T>(value)),
value_(WellDefinedConversionOrZero(value, is_valid_)) {}
constexpr bool is_valid() const { return is_valid_; }
constexpr T value() const { return value_; }
};
// Floating points maintain their own validity, but need translation wrappers.
template <typename T>
class CheckedNumericState<T, NUMERIC_FLOATING> {
private:
T value_;
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrNaN(const Src value,
const bool is_valid) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
NUMERIC_RANGE_CONTAINED ||
is_valid)
? static_cast<T>(value)
: std::numeric_limits<T>::quiet_NaN();
}
public:
template <typename Src, NumericRepresentation type>
friend class CheckedNumericState;
constexpr CheckedNumericState() : value_(0.0) {}
template <typename Src>
constexpr CheckedNumericState(Src value, bool is_valid)
: value_(WellDefinedConversionOrNaN(value, is_valid)) {}
template <typename Src>
constexpr explicit CheckedNumericState(Src value)
: value_(WellDefinedConversionOrNaN(
value,
IsValueInRangeForNumericType<T>(value))) {}
// Copy constructor.
template <typename Src>
constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
: value_(WellDefinedConversionOrNaN(
rhs.value(),
rhs.is_valid() && IsValueInRangeForNumericType<T>(rhs.value()))) {}
constexpr bool is_valid() const {
// Written this way because std::isfinite is not reliably constexpr.
return MustTreatAsConstexpr(value_)
? value_ <= std::numeric_limits<T>::max() &&
value_ >= std::numeric_limits<T>::lowest()
: std::isfinite(value_);
}
constexpr T value() const { return value_; }
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_CHECKED_MATH_IMPL_H_

View File

@@ -1,264 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CLAMPED_MATH_H_
#define BASE_NUMERICS_CLAMPED_MATH_H_
#include <stddef.h>
#include <limits>
#include <type_traits>
#include "base/numerics/clamped_math_impl.h"
namespace base {
namespace internal {
template <typename T>
class ClampedNumeric {
static_assert(std::is_arithmetic<T>::value,
"ClampedNumeric<T>: T must be a numeric type.");
public:
using type = T;
constexpr ClampedNumeric() : value_(0) {}
// Copy constructor.
template <typename Src>
constexpr ClampedNumeric(const ClampedNumeric<Src>& rhs)
: value_(saturated_cast<T>(rhs.value_)) {}
template <typename Src>
friend class ClampedNumeric;
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to ClampedNumerics to make them easier to use.
template <typename Src>
constexpr ClampedNumeric(Src value) // NOLINT(runtime/explicit)
: value_(saturated_cast<T>(value)) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
}
// This is not an explicit constructor because we want a seamless conversion
// from StrictNumeric types.
template <typename Src>
constexpr ClampedNumeric(
StrictNumeric<Src> value) // NOLINT(runtime/explicit)
: value_(saturated_cast<T>(static_cast<Src>(value))) {}
// Returns a ClampedNumeric of the specified type, cast from the current
// ClampedNumeric, and saturated to the destination type.
template <typename Dst>
constexpr ClampedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
return *this;
}
// Prototypes for the supported arithmetic operator overloads.
template <typename Src>
constexpr ClampedNumeric& operator+=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator-=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator*=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator/=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator%=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator<<=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator>>=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator&=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator|=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator^=(const Src rhs);
constexpr ClampedNumeric operator-() const {
// The negation of two's complement int min is int min, so that's the
// only overflow case where we will saturate.
return ClampedNumeric<T>(SaturatedNegWrapper(value_));
}
constexpr ClampedNumeric operator~() const {
return ClampedNumeric<decltype(InvertWrapper(T()))>(InvertWrapper(value_));
}
constexpr ClampedNumeric Abs() const {
// The negation of two's complement int min is int min, so that's the
// only overflow case where we will saturate.
return ClampedNumeric<T>(SaturatedAbsWrapper(value_));
}
template <typename U>
constexpr ClampedNumeric<typename MathWrapper<ClampedMaxOp, T, U>::type> Max(
const U rhs) const {
using result_type = typename MathWrapper<ClampedMaxOp, T, U>::type;
return ClampedNumeric<result_type>(
ClampedMaxOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
}
template <typename U>
constexpr ClampedNumeric<typename MathWrapper<ClampedMinOp, T, U>::type> Min(
const U rhs) const {
using result_type = typename MathWrapper<ClampedMinOp, T, U>::type;
return ClampedNumeric<result_type>(
ClampedMinOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
}
// This function is available only for integral types. It returns an unsigned
// integer of the same width as the source type, containing the absolute value
// of the source, and properly handling signed min.
constexpr ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>
UnsignedAbs() const {
return ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>(
SafeUnsignedAbs(value_));
}
constexpr ClampedNumeric& operator++() {
*this += 1;
return *this;
}
constexpr ClampedNumeric operator++(int) {
ClampedNumeric value = *this;
*this += 1;
return value;
}
constexpr ClampedNumeric& operator--() {
*this -= 1;
return *this;
}
constexpr ClampedNumeric operator--(int) {
ClampedNumeric value = *this;
*this -= 1;
return value;
}
// These perform the actual math operations on the ClampedNumerics.
// Binary arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
static constexpr ClampedNumeric MathOp(const L lhs, const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return ClampedNumeric<T>(
Math::template Do<T>(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs)));
}
// Assignment arithmetic operations.
template <template <typename, typename, typename> class M, typename R>
constexpr ClampedNumeric& MathOp(const R rhs) {
using Math = typename MathWrapper<M, T, R>::math;
*this =
ClampedNumeric<T>(Math::template Do<T>(value_, Wrapper<R>::value(rhs)));
return *this;
}
template <typename Dst>
constexpr operator Dst() const {
return saturated_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(
value_);
}
// This method extracts the raw integer value without saturating it to the
// destination type as the conversion operator does. This is useful when
// e.g. assigning to an auto type or passing as a deduced template parameter.
constexpr T RawValue() const { return value_; }
private:
T value_;
// These wrappers allow us to handle state the same way for both
// ClampedNumeric and POD arithmetic types.
template <typename Src>
struct Wrapper {
static constexpr Src value(Src value) {
return static_cast<typename UnderlyingType<Src>::type>(value);
}
};
};
// Convience wrapper to return a new ClampedNumeric from the provided arithmetic
// or ClampedNumericType.
template <typename T>
constexpr ClampedNumeric<typename UnderlyingType<T>::type> MakeClampedNum(
const T value) {
return value;
}
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
// Overload the ostream output operator to make logging work nicely.
template <typename T>
std::ostream& operator<<(std::ostream& os, const ClampedNumeric<T>& value) {
os << static_cast<T>(value);
return os;
}
#endif
// These implement the variadic wrapper for the math operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
constexpr ClampedNumeric<typename MathWrapper<M, L, R>::type> ClampMathOp(
const L lhs,
const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return ClampedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
rhs);
}
// General purpose wrapper template for arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
constexpr ClampedNumeric<typename ResultType<M, L, R, Args...>::type>
ClampMathOp(const L lhs, const R rhs, const Args... args) {
return ClampMathOp<M>(ClampMathOp<M>(lhs, rhs), args...);
}
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Add, +, +=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Sub, -, -=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mul, *, *=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Div, /, /=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mod, %, %=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Lsh, <<, <<=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Rsh, >>, >>=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, And, &, &=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Or, |, |=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Xor, ^, ^=)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Max)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Min)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLess, <)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLessOrEqual, <=)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreater, >)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreaterOrEqual, >=)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsEqual, ==)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsNotEqual, !=)
} // namespace internal
using internal::ClampedNumeric;
using internal::MakeClampedNum;
using internal::ClampMax;
using internal::ClampMin;
using internal::ClampAdd;
using internal::ClampSub;
using internal::ClampMul;
using internal::ClampDiv;
using internal::ClampMod;
using internal::ClampLsh;
using internal::ClampRsh;
using internal::ClampAnd;
using internal::ClampOr;
using internal::ClampXor;
} // namespace base
#endif // BASE_NUMERICS_CLAMPED_MATH_H_

View File

@@ -1,341 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
#define BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
#include <stddef.h>
#include <stdint.h>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math_shared_impl.h"
namespace base {
namespace internal {
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
std::is_signed<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return MustTreatAsConstexpr(value) || !ClampedNegFastOp<T>::is_supported
? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
? NegateWrapper(value)
: std::numeric_limits<T>::max())
: ClampedNegFastOp<T>::Do(value);
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
!std::is_signed<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return T(0);
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return -value;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr T SaturatedAbsWrapper(T value) {
// The calculation below is a static identity for unsigned types, but for
// signed integer types it provides a non-branching, saturated absolute value.
// This works because SafeUnsignedAbs() returns an unsigned type, which can
// represent the absolute value of all negative numbers of an equal-width
// integer type. The call to IsValueNegative() then detects overflow in the
// special case of numeric_limits<T>::min(), by evaluating the bit pattern as
// a signed integer value. If it is the overflow case, we end up subtracting
// one from the unsigned result, thus saturating to numeric_limits<T>::max().
return static_cast<T>(SafeUnsignedAbs(value) -
IsValueNegative<T>(SafeUnsignedAbs(value)));
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T SaturatedAbsWrapper(T value) {
return value < 0 ? -value : value;
}
template <typename T, typename U, class Enable = void>
struct ClampedAddOp {};
template <typename T, typename U>
struct ClampedAddOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
if (ClampedAddFastOp<T, U>::is_supported)
return ClampedAddFastOp<T, U>::template Do<V>(x, y);
static_assert(std::is_same<V, result_type>::value ||
IsTypeInRangeForNumericType<U, V>::value,
"The saturation result cannot be determined from the "
"provided types.");
const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
V result = {};
return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
? result
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedSubOp {};
template <typename T, typename U>
struct ClampedSubOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (ClampedSubFastOp<T, U>::is_supported)
return ClampedSubFastOp<T, U>::template Do<V>(x, y);
static_assert(std::is_same<V, result_type>::value ||
IsTypeInRangeForNumericType<U, V>::value,
"The saturation result cannot be determined from the "
"provided types.");
const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
V result = {};
return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
? result
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedMulOp {};
template <typename T, typename U>
struct ClampedMulOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (ClampedMulFastOp<T, U>::is_supported)
return ClampedMulFastOp<T, U>::template Do<V>(x, y);
V result = {};
const V saturated =
CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
? result
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedDivOp {};
template <typename T, typename U>
struct ClampedDivOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
V result = {};
if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result))))
return result;
// Saturation goes to max, min, or NaN (if x is zero).
return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
: SaturationDefaultLimits<V>::NaN();
}
};
template <typename T, typename U, class Enable = void>
struct ClampedModOp {};
template <typename T, typename U>
struct ClampedModOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
V result = {};
return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
? result
: x;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedLshOp {};
// Left shift. Non-zero values saturate in the direction of the sign. A zero
// shifted by any value always results in zero.
template <typename T, typename U>
struct ClampedLshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V = result_type>
static constexpr V Do(T x, U shift) {
static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
// Shift as unsigned to avoid undefined behavior.
V result = static_cast<V>(as_unsigned(x) << shift);
// If the shift can be reversed, we know it was valid.
if (BASE_NUMERICS_LIKELY(result >> shift == x))
return result;
}
return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedRshOp {};
// Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
template <typename T, typename U>
struct ClampedRshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V = result_type>
static constexpr V Do(T x, U shift) {
static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
// Signed right shift is odd, because it saturates to -1 or 0.
const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
? saturated_cast<V>(x >> shift)
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedAndOp {};
template <typename T, typename U>
struct ClampedAndOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) & static_cast<result_type>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedOrOp {};
// For simplicity we promote to unsigned integers.
template <typename T, typename U>
struct ClampedOrOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) | static_cast<result_type>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedXorOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
struct ClampedXorOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) ^ static_cast<result_type>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedMaxOp {};
template <typename T, typename U>
struct ClampedMaxOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
: saturated_cast<V>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedMinOp {};
template <typename T, typename U>
struct ClampedMinOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename LowestValuePromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
: saturated_cast<V>(y);
}
};
// This is just boilerplate that wraps the standard floating point arithmetic.
// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
template <typename T, typename U> \
struct Clamped##NAME##Op< \
T, U, \
typename std::enable_if<std::is_floating_point<T>::value || \
std::is_floating_point<U>::value>::type> { \
using result_type = typename MaxExponentPromotion<T, U>::type; \
template <typename V = result_type> \
static constexpr V Do(T x, U y) { \
return saturated_cast<V>(x OP y); \
} \
};
BASE_FLOAT_ARITHMETIC_OPS(Add, +)
BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
BASE_FLOAT_ARITHMETIC_OPS(Div, /)
#undef BASE_FLOAT_ARITHMETIC_OPS
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_CLAMPED_MATH_IMPL_H_

View File

@@ -1,19 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_MATH_CONSTANTS_H_
#define BASE_NUMERICS_MATH_CONSTANTS_H_
namespace base {
constexpr double kPiDouble = 3.14159265358979323846;
constexpr float kPiFloat = 3.14159265358979323846f;
// The mean acceleration due to gravity on Earth in m/s^2.
constexpr double kMeanGravityDouble = 9.80665;
constexpr float kMeanGravityFloat = 9.80665f;
} // namespace base
#endif // BASE_NUMERICS_MATH_CONSTANTS_H_

View File

@@ -1,27 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_RANGES_H_
#define BASE_NUMERICS_RANGES_H_
#include <algorithm>
#include <cmath>
namespace base {
// To be replaced with std::clamp() from C++17, someday.
template <class T>
constexpr const T& ClampToRange(const T& value, const T& min, const T& max) {
return std::min(std::max(value, min), max);
}
template <typename T>
constexpr bool IsApproximatelyEqual(T lhs, T rhs, T tolerance) {
static_assert(std::is_arithmetic<T>::value, "Argument must be arithmetic");
return std::abs(rhs - lhs) <= tolerance;
}
} // namespace base
#endif // BASE_NUMERICS_RANGES_H_

View File

@@ -1,358 +0,0 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_H_
#include <stddef.h>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions_impl.h"
#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
#include "base/numerics/safe_conversions_arm_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
#endif
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
#include <ostream>
#endif
namespace base {
namespace internal {
#if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static const bool is_supported = false;
static constexpr Dst Do(Src) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<Dst>();
}
};
#endif // BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
#undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
// The following special case a few specific integer conversions where we can
// eke out better performance than range checking.
template <typename Dst, typename Src, typename Enable = void>
struct IsValueInRangeFastOp {
static const bool is_supported = false;
static constexpr bool Do(Src value) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
// Signed to signed range comparison.
template <typename Dst, typename Src>
struct IsValueInRangeFastOp<
Dst,
Src,
typename std::enable_if<
std::is_integral<Dst>::value && std::is_integral<Src>::value &&
std::is_signed<Dst>::value && std::is_signed<Src>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
static const bool is_supported = true;
static constexpr bool Do(Src value) {
// Just downcast to the smaller type, sign extend it back to the original
// type, and then see if it matches the original value.
return value == static_cast<Dst>(value);
}
};
// Signed to unsigned range comparison.
template <typename Dst, typename Src>
struct IsValueInRangeFastOp<
Dst,
Src,
typename std::enable_if<
std::is_integral<Dst>::value && std::is_integral<Src>::value &&
!std::is_signed<Dst>::value && std::is_signed<Src>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
static const bool is_supported = true;
static constexpr bool Do(Src value) {
// We cast a signed as unsigned to overflow negative values to the top,
// then compare against whichever maximum is smaller, as our upper bound.
return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
}
};
// Convenience function that returns true if the supplied value is in range
// for the destination type.
template <typename Dst, typename Src>
constexpr bool IsValueInRangeForNumericType(Src value) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
static_cast<SrcType>(value))
: internal::DstRangeRelationToSrcRange<Dst>(
static_cast<SrcType>(value))
.IsValid();
}
// checked_cast<> is analogous to static_cast<> for numeric types,
// except that it CHECKs that the specified numeric conversion will not
// overflow or underflow. NaN source will always trigger a CHECK.
template <typename Dst,
class CheckHandler = internal::CheckOnFailure,
typename Src>
constexpr Dst checked_cast(Src value) {
// This throws a compile-time error on evaluating the constexpr if it can be
// determined at compile-time as failing, otherwise it will CHECK at runtime.
using SrcType = typename internal::UnderlyingType<Src>::type;
return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
? static_cast<Dst>(static_cast<SrcType>(value))
: CheckHandler::template HandleFailure<Dst>();
}
// Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
// You may provide your own limits (e.g. to saturated_cast) so long as you
// implement all of the static constexpr member functions in the class below.
template <typename T>
struct SaturationDefaultLimits : public std::numeric_limits<T> {
static constexpr T NaN() {
return std::numeric_limits<T>::has_quiet_NaN
? std::numeric_limits<T>::quiet_NaN()
: T();
}
using std::numeric_limits<T>::max;
static constexpr T Overflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity()
: std::numeric_limits<T>::max();
}
using std::numeric_limits<T>::lowest;
static constexpr T Underflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity() * -1
: std::numeric_limits<T>::lowest();
}
};
template <typename Dst, template <typename> class S, typename Src>
constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
// For some reason clang generates much better code when the branch is
// structured exactly this way, rather than a sequence of checks.
return !constraint.IsOverflowFlagSet()
? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value)
: S<Dst>::Underflow())
// Skip this check for integral Src, which cannot be NaN.
: (std::is_integral<Src>::value || !constraint.IsUnderflowFlagSet()
? S<Dst>::Overflow()
: S<Dst>::NaN());
}
// We can reduce the number of conditions and get slightly better performance
// for normal signed and unsigned integer ranges. And in the specific case of
// Arm, we can use the optimized saturation instructions.
template <typename Dst, typename Src, typename Enable = void>
struct SaturateFastOp {
static const bool is_supported = false;
static constexpr Dst Do(Src value) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<Dst>();
}
};
template <typename Dst, typename Src>
struct SaturateFastOp<
Dst,
Src,
typename std::enable_if<std::is_integral<Src>::value &&
std::is_integral<Dst>::value &&
SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
static const bool is_supported = true;
static Dst Do(Src value) { return SaturateFastAsmOp<Dst, Src>::Do(value); }
};
template <typename Dst, typename Src>
struct SaturateFastOp<
Dst,
Src,
typename std::enable_if<std::is_integral<Src>::value &&
std::is_integral<Dst>::value &&
!SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
static const bool is_supported = true;
static Dst Do(Src value) {
// The exact order of the following is structured to hit the correct
// optimization heuristics across compilers. Do not change without
// checking the emitted code.
Dst saturated = CommonMaxOrMin<Dst, Src>(
IsMaxInRangeForNumericType<Dst, Src>() ||
(!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
? static_cast<Dst>(value)
: saturated;
}
};
// saturated_cast<> is analogous to static_cast<> for numeric types, except
// that the specified numeric conversion will saturate by default rather than
// overflow or underflow, and NaN assignment to an integral will return 0.
// All boundary condition behaviors can be overriden with a custom handler.
template <typename Dst,
template <typename> class SaturationHandler = SaturationDefaultLimits,
typename Src>
constexpr Dst saturated_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type;
return !IsCompileTimeConstant(value) &&
SaturateFastOp<Dst, SrcType>::is_supported &&
std::is_same<SaturationHandler<Dst>,
SaturationDefaultLimits<Dst>>::value
? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
: saturated_cast_impl<Dst, SaturationHandler, SrcType>(
static_cast<SrcType>(value),
DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
static_cast<SrcType>(value)));
}
// strict_cast<> is analogous to static_cast<> for numeric types, except that
// it will cause a compile failure if the destination type is not large enough
// to contain any value in the source type. It performs no runtime checking.
template <typename Dst, typename Src>
constexpr Dst strict_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type;
static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
// If you got here from a compiler error, it's because you tried to assign
// from a source type to a destination type that has insufficient range.
// The solution may be to change the destination type you're assigning to,
// and use one large enough to represent the source.
// Alternatively, you may be better served with the checked_cast<> or
// saturated_cast<> template functions for your particular use case.
static_assert(StaticDstRangeRelationToSrcRange<Dst, SrcType>::value ==
NUMERIC_RANGE_CONTAINED,
"The source type is out of range for the destination type. "
"Please see strict_cast<> comments for more information.");
return static_cast<Dst>(static_cast<SrcType>(value));
}
// Some wrappers to statically check that a type is in range.
template <typename Dst, typename Src, class Enable = void>
struct IsNumericRangeContained {
static const bool value = false;
};
template <typename Dst, typename Src>
struct IsNumericRangeContained<
Dst,
Src,
typename std::enable_if<ArithmeticOrUnderlyingEnum<Dst>::value &&
ArithmeticOrUnderlyingEnum<Src>::value>::type> {
static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
NUMERIC_RANGE_CONTAINED;
};
// StrictNumeric implements compile time range checking between numeric types by
// wrapping assignment operations in a strict_cast. This class is intended to be
// used for function arguments and return types, to ensure the destination type
// can always contain the source type. This is essentially the same as enforcing
// -Wconversion in gcc and C4302 warnings on MSVC, but it can be applied
// incrementally at API boundaries, making it easier to convert code so that it
// compiles cleanly with truncation warnings enabled.
// This template should introduce no runtime overhead, but it also provides no
// runtime checking of any of the associated mathematical operations. Use
// CheckedNumeric for runtime range checks of the actual value being assigned.
template <typename T>
class StrictNumeric {
public:
using type = T;
constexpr StrictNumeric() : value_(0) {}
// Copy constructor.
template <typename Src>
constexpr StrictNumeric(const StrictNumeric<Src>& rhs)
: value_(strict_cast<T>(rhs.value_)) {}
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to StrictNumerics to make them easier to use.
template <typename Src>
constexpr StrictNumeric(Src value) // NOLINT(runtime/explicit)
: value_(strict_cast<T>(value)) {}
// If you got here from a compiler error, it's because you tried to assign
// from a source type to a destination type that has insufficient range.
// The solution may be to change the destination type you're assigning to,
// and use one large enough to represent the source.
// If you're assigning from a CheckedNumeric<> class, you may be able to use
// the AssignIfValid() member function, specify a narrower destination type to
// the member value functions (e.g. val.template ValueOrDie<Dst>()), use one
// of the value helper functions (e.g. ValueOrDieForType<Dst>(val)).
// If you've encountered an _ambiguous overload_ you can use a static_cast<>
// to explicitly cast the result to the destination type.
// If none of that works, you may be better served with the checked_cast<> or
// saturated_cast<> template functions for your particular use case.
template <typename Dst,
typename std::enable_if<
IsNumericRangeContained<Dst, T>::value>::type* = nullptr>
constexpr operator Dst() const {
return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
}
private:
const T value_;
};
// Convience wrapper returns a StrictNumeric from the provided arithmetic type.
template <typename T>
constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(
const T value) {
return value;
}
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
// Overload the ostream output operator to make logging work nicely.
template <typename T>
std::ostream& operator<<(std::ostream& os, const StrictNumeric<T>& value) {
os << static_cast<T>(value);
return os;
}
#endif
#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP) \
template <typename L, typename R, \
typename std::enable_if< \
internal::Is##CLASS##Op<L, R>::value>::type* = nullptr> \
constexpr bool operator OP(const L lhs, const R rhs) { \
return SafeCompare<NAME, typename UnderlyingType<L>::type, \
typename UnderlyingType<R>::type>(lhs, rhs); \
}
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLessOrEqual, <=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreater, >)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreaterOrEqual, >=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsEqual, ==)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=)
} // namespace internal
using internal::as_signed;
using internal::as_unsigned;
using internal::checked_cast;
using internal::strict_cast;
using internal::saturated_cast;
using internal::SafeUnsignedAbs;
using internal::StrictNumeric;
using internal::MakeStrictNum;
using internal::IsValueInRangeForNumericType;
using internal::IsTypeInRangeForNumericType;
using internal::IsValueNegative;
// Explicitly make a shorter size_t alias for convenience.
using SizeT = StrictNumeric<size_t>;
} // namespace base
#endif // BASE_NUMERICS_SAFE_CONVERSIONS_H_

View File

@@ -1,51 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions_impl.h"
namespace base {
namespace internal {
// Fast saturation to a destination type.
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static constexpr bool is_supported =
std::is_signed<Src>::value && std::is_integral<Dst>::value &&
std::is_integral<Src>::value &&
IntegerBitsPlusSign<Src>::value <= IntegerBitsPlusSign<int32_t>::value &&
IntegerBitsPlusSign<Dst>::value <= IntegerBitsPlusSign<int32_t>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value;
__attribute__((always_inline)) static Dst Do(Src value) {
int32_t src = value;
typename std::conditional<std::is_signed<Dst>::value, int32_t,
uint32_t>::type result;
if (std::is_signed<Dst>::value) {
asm("ssat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
: [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value <= 32
? IntegerBitsPlusSign<Dst>::value
: 32));
} else {
asm("usat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
: [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value < 32
? IntegerBitsPlusSign<Dst>::value
: 31));
}
return static_cast<Dst>(result);
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_

View File

@@ -1,850 +0,0 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
#include <stdint.h>
#include <limits>
#include <type_traits>
#if defined(__GNUC__) || defined(__clang__)
#define BASE_NUMERICS_LIKELY(x) __builtin_expect(!!(x), 1)
#define BASE_NUMERICS_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define BASE_NUMERICS_LIKELY(x) (x)
#define BASE_NUMERICS_UNLIKELY(x) (x)
#endif
namespace base {
namespace internal {
// The std library doesn't provide a binary max_exponent for integers, however
// we can compute an analog using std::numeric_limits<>::digits.
template <typename NumericType>
struct MaxExponent {
static const int value = std::is_floating_point<NumericType>::value
? std::numeric_limits<NumericType>::max_exponent
: std::numeric_limits<NumericType>::digits + 1;
};
// The number of bits (including the sign) in an integer. Eliminates sizeof
// hacks.
template <typename NumericType>
struct IntegerBitsPlusSign {
static const int value = std::numeric_limits<NumericType>::digits +
std::is_signed<NumericType>::value;
};
// Helper templates for integer manipulations.
template <typename Integer>
struct PositionOfSignBit {
static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
};
// Determines if a numeric value is negative without throwing compiler
// warnings on: unsigned(value) < 0.
template <typename T,
typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T value) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return value < 0;
}
template <typename T,
typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return false;
}
// This performs a fast negation, returning a signed value. It works on unsigned
// arguments, but probably doesn't do what you want for any unsigned value
// larger than max / 2 + 1 (i.e. signed min cast to unsigned).
template <typename T>
constexpr typename std::make_signed<T>::type ConditionalNegate(
T x,
bool is_negative) {
static_assert(std::is_integral<T>::value, "Type must be integral");
using SignedT = typename std::make_signed<T>::type;
using UnsignedT = typename std::make_unsigned<T>::type;
return static_cast<SignedT>(
(static_cast<UnsignedT>(x) ^ -SignedT(is_negative)) + is_negative);
}
// This performs a safe, absolute value via unsigned overflow.
template <typename T>
constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
static_assert(std::is_integral<T>::value, "Type must be integral");
using UnsignedT = typename std::make_unsigned<T>::type;
return IsValueNegative(value) ? 0 - static_cast<UnsignedT>(value)
: static_cast<UnsignedT>(value);
}
// This allows us to switch paths on known compile-time constants.
#if defined(__clang__) || defined(__GNUC__)
constexpr bool CanDetectCompileTimeConstant() {
return true;
}
template <typename T>
constexpr bool IsCompileTimeConstant(const T v) {
return __builtin_constant_p(v);
}
#else
constexpr bool CanDetectCompileTimeConstant() {
return false;
}
template <typename T>
constexpr bool IsCompileTimeConstant(const T) {
return false;
}
#endif
template <typename T>
constexpr bool MustTreatAsConstexpr(const T v) {
// Either we can't detect a compile-time constant, and must always use the
// constexpr path, or we know we have a compile-time constant.
return !CanDetectCompileTimeConstant() || IsCompileTimeConstant(v);
}
// Forces a crash, like a CHECK(false). Used for numeric boundary errors.
// Also used in a constexpr template to trigger a compilation failure on
// an error condition.
struct CheckOnFailure {
template <typename T>
static T HandleFailure() {
#if defined(_MSC_VER)
__debugbreak();
#elif defined(__GNUC__) || defined(__clang__)
__builtin_trap();
#else
((void)(*(volatile char*)0 = 0));
#endif
return T();
}
};
enum IntegerRepresentation {
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED
};
// A range for a given nunmeric Src type is contained for a given numeric Dst
// type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and
// numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true.
// We implement this as template specializations rather than simple static
// comparisons to ensure type correctness in our comparisons.
enum NumericRangeRepresentation {
NUMERIC_RANGE_NOT_CONTAINED,
NUMERIC_RANGE_CONTAINED
};
// Helper templates to statically determine if our destination type can contain
// maximum and minimum values represented by the source type.
template <typename Dst,
typename Src,
IntegerRepresentation DstSign = std::is_signed<Dst>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
IntegerRepresentation SrcSign = std::is_signed<Src>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED>
struct StaticDstRangeRelationToSrcRange;
// Same sign: Dst is guaranteed to contain Src only if its range is equal or
// larger.
template <typename Dst, typename Src, IntegerRepresentation Sign>
struct StaticDstRangeRelationToSrcRange<Dst, Src, Sign, Sign> {
static const NumericRangeRepresentation value =
MaxExponent<Dst>::value >= MaxExponent<Src>::value
? NUMERIC_RANGE_CONTAINED
: NUMERIC_RANGE_NOT_CONTAINED;
};
// Unsigned to signed: Dst is guaranteed to contain source only if its range is
// larger.
template <typename Dst, typename Src>
struct StaticDstRangeRelationToSrcRange<Dst,
Src,
INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_UNSIGNED> {
static const NumericRangeRepresentation value =
MaxExponent<Dst>::value > MaxExponent<Src>::value
? NUMERIC_RANGE_CONTAINED
: NUMERIC_RANGE_NOT_CONTAINED;
};
// Signed to unsigned: Dst cannot be statically determined to contain Src.
template <typename Dst, typename Src>
struct StaticDstRangeRelationToSrcRange<Dst,
Src,
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED> {
static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
};
// This class wraps the range constraints as separate booleans so the compiler
// can identify constants and eliminate unused code paths.
class RangeCheck {
public:
constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound)
: is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {}
constexpr RangeCheck() : is_underflow_(0), is_overflow_(0) {}
constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; }
constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; }
constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; }
constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; }
constexpr bool IsOverflowFlagSet() const { return is_overflow_; }
constexpr bool IsUnderflowFlagSet() const { return is_underflow_; }
constexpr bool operator==(const RangeCheck rhs) const {
return is_underflow_ == rhs.is_underflow_ &&
is_overflow_ == rhs.is_overflow_;
}
constexpr bool operator!=(const RangeCheck rhs) const {
return !(*this == rhs);
}
private:
// Do not change the order of these member variables. The integral conversion
// optimization depends on this exact order.
const bool is_underflow_;
const bool is_overflow_;
};
// The following helper template addresses a corner case in range checks for
// conversion from a floating-point type to an integral type of smaller range
// but larger precision (e.g. float -> unsigned). The problem is as follows:
// 1. Integral maximum is always one less than a power of two, so it must be
// truncated to fit the mantissa of the floating point. The direction of
// rounding is implementation defined, but by default it's always IEEE
// floats, which round to nearest and thus result in a value of larger
// magnitude than the integral value.
// Example: float f = UINT_MAX; // f is 4294967296f but UINT_MAX
// // is 4294967295u.
// 2. If the floating point value is equal to the promoted integral maximum
// value, a range check will erroneously pass.
// Example: (4294967296f <= 4294967295u) // This is true due to a precision
// // loss in rounding up to float.
// 3. When the floating point value is then converted to an integral, the
// resulting value is out of range for the target integral type and
// thus is implementation defined.
// Example: unsigned u = (float)INT_MAX; // u will typically overflow to 0.
// To fix this bug we manually truncate the maximum value when the destination
// type is an integral of larger precision than the source floating-point type,
// such that the resulting maximum is represented exactly as a floating point.
template <typename Dst, typename Src, template <typename> class Bounds>
struct NarrowingRange {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = typename std::numeric_limits<Dst>;
// Computes the mask required to make an accurate comparison between types.
static const int kShift =
(MaxExponent<Src>::value > MaxExponent<Dst>::value &&
SrcLimits::digits < DstLimits::digits)
? (DstLimits::digits - SrcLimits::digits)
: 0;
template <
typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
// Masks out the integer bits that are beyond the precision of the
// intermediate type used for comparison.
static constexpr T Adjust(T value) {
static_assert(std::is_same<T, Dst>::value, "");
static_assert(kShift < DstLimits::digits, "");
return static_cast<T>(
ConditionalNegate(SafeUnsignedAbs(value) & ~((T(1) << kShift) - T(1)),
IsValueNegative(value)));
}
template <typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* =
nullptr>
static constexpr T Adjust(T value) {
static_assert(std::is_same<T, Dst>::value, "");
static_assert(kShift == 0, "");
return value;
}
static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); }
static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); }
};
template <typename Dst,
typename Src,
template <typename> class Bounds,
IntegerRepresentation DstSign = std::is_signed<Dst>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
IntegerRepresentation SrcSign = std::is_signed<Src>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
NumericRangeRepresentation DstRange =
StaticDstRangeRelationToSrcRange<Dst, Src>::value>
struct DstRangeRelationToSrcRangeImpl;
// The following templates are for ranges that must be verified at runtime. We
// split it into checks based on signedness to avoid confusing casts and
// compiler warnings on signed an unsigned comparisons.
// Same sign narrowing: The range is contained for normal limits.
template <typename Dst,
typename Src,
template <typename> class Bounds,
IntegerRepresentation DstSign,
IntegerRepresentation SrcSign>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
DstSign,
SrcSign,
NUMERIC_RANGE_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(
static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() ||
static_cast<Dst>(value) >= DstLimits::lowest(),
static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() ||
static_cast<Dst>(value) <= DstLimits::max());
}
};
// Signed to signed narrowing: Both the upper and lower boundaries may be
// exceeded for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_SIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max());
}
};
// Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
// standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_UNSIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(
DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest(),
value <= DstLimits::max());
}
};
// Unsigned to signed: Only the upper bound can be exceeded for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_UNSIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
using Promotion = decltype(Src() + Dst());
return RangeCheck(DstLimits::lowest() <= Dst(0) ||
static_cast<Promotion>(value) >=
static_cast<Promotion>(DstLimits::lowest()),
static_cast<Promotion>(value) <=
static_cast<Promotion>(DstLimits::max()));
}
};
// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
// and any negative value exceeds the lower boundary for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
using Promotion = decltype(Src() + Dst());
return RangeCheck(
value >= Src(0) && (DstLimits::lowest() == 0 ||
static_cast<Dst>(value) >= DstLimits::lowest()),
static_cast<Promotion>(SrcLimits::max()) <=
static_cast<Promotion>(DstLimits::max()) ||
static_cast<Promotion>(value) <=
static_cast<Promotion>(DstLimits::max()));
}
};
// Simple wrapper for statically checking if a type's range is contained.
template <typename Dst, typename Src>
struct IsTypeInRangeForNumericType {
static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
NUMERIC_RANGE_CONTAINED;
};
template <typename Dst,
template <typename> class Bounds = std::numeric_limits,
typename Src>
constexpr RangeCheck DstRangeRelationToSrcRange(Src value) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
}
// Integer promotion templates used by the portable checked integer arithmetic.
template <size_t Size, bool IsSigned>
struct IntegerForDigitsAndSign;
#define INTEGER_FOR_DIGITS_AND_SIGN(I) \
template <> \
struct IntegerForDigitsAndSign<IntegerBitsPlusSign<I>::value, \
std::is_signed<I>::value> { \
using type = I; \
}
INTEGER_FOR_DIGITS_AND_SIGN(int8_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint8_t);
INTEGER_FOR_DIGITS_AND_SIGN(int16_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint16_t);
INTEGER_FOR_DIGITS_AND_SIGN(int32_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint32_t);
INTEGER_FOR_DIGITS_AND_SIGN(int64_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint64_t);
#undef INTEGER_FOR_DIGITS_AND_SIGN
// WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to
// support 128-bit math, then the ArithmeticPromotion template below will need
// to be updated (or more likely replaced with a decltype expression).
static_assert(IntegerBitsPlusSign<intmax_t>::value == 64,
"Max integer size not supported for this toolchain.");
template <typename Integer, bool IsSigned = std::is_signed<Integer>::value>
struct TwiceWiderInteger {
using type =
typename IntegerForDigitsAndSign<IntegerBitsPlusSign<Integer>::value * 2,
IsSigned>::type;
};
enum ArithmeticPromotionCategory {
LEFT_PROMOTION, // Use the type of the left-hand argument.
RIGHT_PROMOTION // Use the type of the right-hand argument.
};
// Determines the type that can represent the largest positive value.
template <typename Lhs,
typename Rhs,
ArithmeticPromotionCategory Promotion =
(MaxExponent<Lhs>::value > MaxExponent<Rhs>::value)
? LEFT_PROMOTION
: RIGHT_PROMOTION>
struct MaxExponentPromotion;
template <typename Lhs, typename Rhs>
struct MaxExponentPromotion<Lhs, Rhs, LEFT_PROMOTION> {
using type = Lhs;
};
template <typename Lhs, typename Rhs>
struct MaxExponentPromotion<Lhs, Rhs, RIGHT_PROMOTION> {
using type = Rhs;
};
// Determines the type that can represent the lowest arithmetic value.
template <typename Lhs,
typename Rhs,
ArithmeticPromotionCategory Promotion =
std::is_signed<Lhs>::value
? (std::is_signed<Rhs>::value
? (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value
? LEFT_PROMOTION
: RIGHT_PROMOTION)
: LEFT_PROMOTION)
: (std::is_signed<Rhs>::value
? RIGHT_PROMOTION
: (MaxExponent<Lhs>::value < MaxExponent<Rhs>::value
? LEFT_PROMOTION
: RIGHT_PROMOTION))>
struct LowestValuePromotion;
template <typename Lhs, typename Rhs>
struct LowestValuePromotion<Lhs, Rhs, LEFT_PROMOTION> {
using type = Lhs;
};
template <typename Lhs, typename Rhs>
struct LowestValuePromotion<Lhs, Rhs, RIGHT_PROMOTION> {
using type = Rhs;
};
// Determines the type that is best able to represent an arithmetic result.
template <
typename Lhs,
typename Rhs = Lhs,
bool is_intmax_type =
std::is_integral<typename MaxExponentPromotion<Lhs, Rhs>::type>::value&&
IntegerBitsPlusSign<typename MaxExponentPromotion<Lhs, Rhs>::type>::
value == IntegerBitsPlusSign<intmax_t>::value,
bool is_max_exponent =
StaticDstRangeRelationToSrcRange<
typename MaxExponentPromotion<Lhs, Rhs>::type,
Lhs>::value ==
NUMERIC_RANGE_CONTAINED&& StaticDstRangeRelationToSrcRange<
typename MaxExponentPromotion<Lhs, Rhs>::type,
Rhs>::value == NUMERIC_RANGE_CONTAINED>
struct BigEnoughPromotion;
// The side with the max exponent is big enough.
template <typename Lhs, typename Rhs, bool is_intmax_type>
struct BigEnoughPromotion<Lhs, Rhs, is_intmax_type, true> {
using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
static const bool is_contained = true;
};
// We can use a twice wider type to fit.
template <typename Lhs, typename Rhs>
struct BigEnoughPromotion<Lhs, Rhs, false, false> {
using type =
typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value>::type;
static const bool is_contained = true;
};
// No type is large enough.
template <typename Lhs, typename Rhs>
struct BigEnoughPromotion<Lhs, Rhs, true, false> {
using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
static const bool is_contained = false;
};
// We can statically check if operations on the provided types can wrap, so we
// can skip the checked operations if they're not needed. So, for an integer we
// care if the destination type preserves the sign and is twice the width of
// the source.
template <typename T, typename Lhs, typename Rhs = Lhs>
struct IsIntegerArithmeticSafe {
static const bool value =
!std::is_floating_point<T>::value &&
!std::is_floating_point<Lhs>::value &&
!std::is_floating_point<Rhs>::value &&
std::is_signed<T>::value >= std::is_signed<Lhs>::value &&
IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Lhs>::value) &&
std::is_signed<T>::value >= std::is_signed<Rhs>::value &&
IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Rhs>::value);
};
// Promotes to a type that can represent any possible result of a binary
// arithmetic operation with the source types.
template <typename Lhs,
typename Rhs,
bool is_promotion_possible = IsIntegerArithmeticSafe<
typename std::conditional<std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value,
intmax_t,
uintmax_t>::type,
typename MaxExponentPromotion<Lhs, Rhs>::type>::value>
struct FastIntegerArithmeticPromotion;
template <typename Lhs, typename Rhs>
struct FastIntegerArithmeticPromotion<Lhs, Rhs, true> {
using type =
typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value>::type;
static_assert(IsIntegerArithmeticSafe<type, Lhs, Rhs>::value, "");
static const bool is_contained = true;
};
template <typename Lhs, typename Rhs>
struct FastIntegerArithmeticPromotion<Lhs, Rhs, false> {
using type = typename BigEnoughPromotion<Lhs, Rhs>::type;
static const bool is_contained = false;
};
// Extracts the underlying type from an enum.
template <typename T, bool is_enum = std::is_enum<T>::value>
struct ArithmeticOrUnderlyingEnum;
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, true> {
using type = typename std::underlying_type<T>::type;
static const bool value = std::is_arithmetic<type>::value;
};
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, false> {
using type = T;
static const bool value = std::is_arithmetic<type>::value;
};
// The following are helper templates used in the CheckedNumeric class.
template <typename T>
class CheckedNumeric;
template <typename T>
class ClampedNumeric;
template <typename T>
class StrictNumeric;
// Used to treat CheckedNumeric and arithmetic underlying types the same.
template <typename T>
struct UnderlyingType {
using type = typename ArithmeticOrUnderlyingEnum<T>::type;
static const bool is_numeric = std::is_arithmetic<type>::value;
static const bool is_checked = false;
static const bool is_clamped = false;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<CheckedNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = true;
static const bool is_clamped = false;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<ClampedNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = false;
static const bool is_clamped = true;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<StrictNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = false;
static const bool is_clamped = false;
static const bool is_strict = true;
};
template <typename L, typename R>
struct IsCheckedOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
};
template <typename L, typename R>
struct IsClampedOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped) &&
!(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
};
template <typename L, typename R>
struct IsStrictOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict) &&
!(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked) &&
!(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped);
};
// as_signed<> returns the supplied integral value (or integral castable
// Numeric template) cast as a signed integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t)
template <typename Src>
constexpr typename std::make_signed<
typename base::internal::UnderlyingType<Src>::type>::type
as_signed(const Src value) {
static_assert(std::is_integral<decltype(as_signed(value))>::value,
"Argument must be a signed or unsigned integer type.");
return static_cast<decltype(as_signed(value))>(value);
}
// as_unsigned<> returns the supplied integral value (or integral castable
// Numeric template) cast as an unsigned integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t)
template <typename Src>
constexpr typename std::make_unsigned<
typename base::internal::UnderlyingType<Src>::type>::type
as_unsigned(const Src value) {
static_assert(std::is_integral<decltype(as_unsigned(value))>::value,
"Argument must be a signed or unsigned integer type.");
return static_cast<decltype(as_unsigned(value))>(value);
}
template <typename L, typename R>
constexpr bool IsLessImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsUnderflow() || r_range.IsOverflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) <
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsLess {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsLessImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsLessOrEqualImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsUnderflow() || r_range.IsOverflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) <=
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsLessOrEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsLessOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsGreaterImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsOverflow() || r_range.IsUnderflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) >
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsGreater {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsGreaterImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsGreaterOrEqualImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsOverflow() || r_range.IsUnderflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) >=
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsGreaterOrEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsGreaterOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
struct IsEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) ==
DstRangeRelationToSrcRange<L>(rhs) &&
static_cast<decltype(lhs + rhs)>(lhs) ==
static_cast<decltype(lhs + rhs)>(rhs);
}
};
template <typename L, typename R>
struct IsNotEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) !=
DstRangeRelationToSrcRange<L>(rhs) ||
static_cast<decltype(lhs + rhs)>(lhs) !=
static_cast<decltype(lhs + rhs)>(rhs);
}
};
// These perform the actual math operations on the CheckedNumerics.
// Binary arithmetic operations.
template <template <typename, typename> class C, typename L, typename R>
constexpr bool SafeCompare(const L lhs, const R rhs) {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
using Promotion = BigEnoughPromotion<L, R>;
using BigType = typename Promotion::type;
return Promotion::is_contained
// Force to a larger type for speed if both are contained.
? C<BigType, BigType>::Test(
static_cast<BigType>(static_cast<L>(lhs)),
static_cast<BigType>(static_cast<R>(rhs)))
// Let the template functions figure it out for mixed types.
: C<L, R>::Test(lhs, rhs);
}
template <typename Dst, typename Src>
constexpr bool IsMaxInRangeForNumericType() {
return IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
std::numeric_limits<Src>::max());
}
template <typename Dst, typename Src>
constexpr bool IsMinInRangeForNumericType() {
return IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
std::numeric_limits<Src>::lowest());
}
template <typename Dst, typename Src>
constexpr Dst CommonMax() {
return !IsMaxInRangeForNumericType<Dst, Src>()
? Dst(std::numeric_limits<Dst>::max())
: Dst(std::numeric_limits<Src>::max());
}
template <typename Dst, typename Src>
constexpr Dst CommonMin() {
return !IsMinInRangeForNumericType<Dst, Src>()
? Dst(std::numeric_limits<Dst>::lowest())
: Dst(std::numeric_limits<Src>::lowest());
}
// This is a wrapper to generate return the max or min for a supplied type.
// If the argument is false, the returned value is the maximum. If true the
// returned value is the minimum.
template <typename Dst, typename Src = Dst>
constexpr Dst CommonMaxOrMin(bool is_min) {
return is_min ? CommonMin<Dst, Src>() : CommonMax<Dst, Src>();
}
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_

View File

@@ -1,12 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_H_
#define BASE_NUMERICS_SAFE_MATH_H_
#include "base/numerics/checked_math.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#endif // BASE_NUMERICS_SAFE_MATH_H_

View File

@@ -1,122 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
#define BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
namespace base {
namespace internal {
template <typename T, typename U>
struct CheckedMulFastAsmOp {
static const bool is_supported =
FastIntegerArithmeticPromotion<T, U>::is_contained;
// The following is much more efficient than the Clang and GCC builtins for
// performing overflow-checked multiplication when a twice wider type is
// available. The below compiles down to 2-3 instructions, depending on the
// width of the types in use.
// As an example, an int32_t multiply compiles to:
// smull r0, r1, r0, r1
// cmp r1, r1, asr #31
// And an int16_t multiply compiles to:
// smulbb r1, r1, r0
// asr r2, r1, #16
// cmp r2, r1, asr #15
template <typename V>
__attribute__((always_inline)) static bool Do(T x, U y, V* result) {
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
Promotion presult;
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
*result = static_cast<V>(presult);
return IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T, typename U>
struct ClampedAddFastAsmOp {
static const bool is_supported =
BigEnoughPromotion<T, U>::is_contained &&
IsTypeInRangeForNumericType<
int32_t,
typename BigEnoughPromotion<T, U>::type>::value;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
if (IsIntegerArithmeticSafe<int, T, U>::value)
return saturated_cast<V>(x + y);
int32_t result;
int32_t x_i32 = checked_cast<int32_t>(x);
int32_t y_i32 = checked_cast<int32_t>(y);
asm("qadd %[result], %[first], %[second]"
: [result] "=r"(result)
: [first] "r"(x_i32), [second] "r"(y_i32));
return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedSubFastAsmOp {
static const bool is_supported =
BigEnoughPromotion<T, U>::is_contained &&
IsTypeInRangeForNumericType<
int32_t,
typename BigEnoughPromotion<T, U>::type>::value;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
if (IsIntegerArithmeticSafe<int, T, U>::value)
return saturated_cast<V>(x - y);
int32_t result;
int32_t x_i32 = checked_cast<int32_t>(x);
int32_t y_i32 = checked_cast<int32_t>(y);
asm("qsub %[result], %[first], %[second]"
: [result] "=r"(result)
: [first] "r"(x_i32), [second] "r"(y_i32));
return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedMulFastAsmOp {
static const bool is_supported = CheckedMulFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// Use the CheckedMulFastAsmOp for full-width 32-bit values, because
// it's fewer instructions than promoting and then saturating.
if (!IsIntegerArithmeticSafe<int32_t, T, U>::value &&
!IsIntegerArithmeticSafe<uint32_t, T, U>::value) {
V result;
if (CheckedMulFastAsmOp<T, U>::Do(x, y, &result))
return result;
return CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
}
assert((FastIntegerArithmeticPromotion<T, U>::is_contained));
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
return saturated_cast<V>(static_cast<Promotion>(x) *
static_cast<Promotion>(y));
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_

View File

@@ -1,157 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
#define BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
#include "base/numerics/safe_math_arm_impl.h"
#define BASE_HAS_ASSEMBLER_SAFE_MATH (1)
#else
#define BASE_HAS_ASSEMBLER_SAFE_MATH (0)
#endif
namespace base {
namespace internal {
// These are the non-functioning boilerplate implementations of the optimized
// safe math routines.
#if !BASE_HAS_ASSEMBLER_SAFE_MATH
template <typename T, typename U>
struct CheckedMulFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct ClampedAddFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedSubFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedMulFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
#endif // BASE_HAS_ASSEMBLER_SAFE_MATH
#undef BASE_HAS_ASSEMBLER_SAFE_MATH
template <typename T, typename U>
struct CheckedAddFastOp {
static const bool is_supported = true;
template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return !__builtin_add_overflow(x, y, result);
}
};
template <typename T, typename U>
struct CheckedSubFastOp {
static const bool is_supported = true;
template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return !__builtin_sub_overflow(x, y, result);
}
};
template <typename T, typename U>
struct CheckedMulFastOp {
#if defined(__clang__)
// TODO(jschuh): Get the Clang runtime library issues sorted out so we can
// support full-width, mixed-sign multiply builtins.
// https://crbug.com/613003
// We can support intptr_t, uintptr_t, or a smaller common type.
static const bool is_supported =
(IsTypeInRangeForNumericType<intptr_t, T>::value &&
IsTypeInRangeForNumericType<intptr_t, U>::value) ||
(IsTypeInRangeForNumericType<uintptr_t, T>::value &&
IsTypeInRangeForNumericType<uintptr_t, U>::value);
#else
static const bool is_supported = true;
#endif
template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return CheckedMulFastAsmOp<T, U>::is_supported
? CheckedMulFastAsmOp<T, U>::Do(x, y, result)
: !__builtin_mul_overflow(x, y, result);
}
};
template <typename T, typename U>
struct ClampedAddFastOp {
static const bool is_supported = ClampedAddFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
return ClampedAddFastAsmOp<T, U>::template Do<V>(x, y);
}
};
template <typename T, typename U>
struct ClampedSubFastOp {
static const bool is_supported = ClampedSubFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
return ClampedSubFastAsmOp<T, U>::template Do<V>(x, y);
}
};
template <typename T, typename U>
struct ClampedMulFastOp {
static const bool is_supported = ClampedMulFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
return ClampedMulFastAsmOp<T, U>::template Do<V>(x, y);
}
};
template <typename T>
struct ClampedNegFastOp {
static const bool is_supported = std::is_signed<T>::value;
__attribute__((always_inline)) static T Do(T value) {
// Use this when there is no assembler path available.
if (!ClampedSubFastAsmOp<T, T>::is_supported) {
T result;
return !__builtin_sub_overflow(T(0), value, &result)
? result
: std::numeric_limits<T>::max();
}
// Fallback to the normal subtraction path.
return ClampedSubFastOp<T, T>::template Do<T>(T(0), value);
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_

View File

@@ -1,240 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
#define BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
#include <stddef.h>
#include <stdint.h>
#include <cassert>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
#ifdef __asmjs__
// Optimized safe math instructions are incompatible with asmjs.
#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
// Where available use builtin math overflow support on Clang and GCC.
#elif !defined(__native_client__) && \
((defined(__clang__) && \
((__clang_major__ > 3) || \
(__clang_major__ == 3 && __clang_minor__ >= 4))) || \
(defined(__GNUC__) && __GNUC__ >= 5))
#include "base/numerics/safe_math_clang_gcc_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
#endif
namespace base {
namespace internal {
// These are the non-functioning boilerplate implementations of the optimized
// safe math routines.
#if !BASE_HAS_OPTIMIZED_SAFE_MATH
template <typename T, typename U>
struct CheckedAddFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct CheckedSubFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct CheckedMulFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct ClampedAddFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedSubFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedMulFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T>
struct ClampedNegFastOp {
static const bool is_supported = false;
static constexpr T Do(T) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<T>();
}
};
#endif // BASE_HAS_OPTIMIZED_SAFE_MATH
#undef BASE_HAS_OPTIMIZED_SAFE_MATH
// This is used for UnsignedAbs, where we need to support floating-point
// template instantiations even though we don't actually support the operations.
// However, there is no corresponding implementation of e.g. SafeUnsignedAbs,
// so the float versions will not compile.
template <typename Numeric,
bool IsInteger = std::is_integral<Numeric>::value,
bool IsFloat = std::is_floating_point<Numeric>::value>
struct UnsignedOrFloatForSize;
template <typename Numeric>
struct UnsignedOrFloatForSize<Numeric, true, false> {
using type = typename std::make_unsigned<Numeric>::type;
};
template <typename Numeric>
struct UnsignedOrFloatForSize<Numeric, false, true> {
using type = Numeric;
};
// Wrap the unary operations to allow SFINAE when instantiating integrals versus
// floating points. These don't perform any overflow checking. Rather, they
// exhibit well-defined overflow semantics and rely on the caller to detect
// if an overflow occured.
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr T NegateWrapper(T value) {
using UnsignedT = typename std::make_unsigned<T>::type;
// This will compile to a NEG on Intel, and is normal negation on ARM.
return static_cast<T>(UnsignedT(0) - static_cast<UnsignedT>(value));
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T NegateWrapper(T value) {
return -value;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
return ~value;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr T AbsWrapper(T value) {
return static_cast<T>(SafeUnsignedAbs(value));
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T AbsWrapper(T value) {
return value < 0 ? -value : value;
}
template <template <typename, typename, typename> class M,
typename L,
typename R>
struct MathWrapper {
using math = M<typename UnderlyingType<L>::type,
typename UnderlyingType<R>::type,
void>;
using type = typename math::result_type;
};
// These variadic templates work out the return types.
// TODO(jschuh): Rip all this out once we have C++14 non-trailing auto support.
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
struct ResultType;
template <template <typename, typename, typename> class M,
typename L,
typename R>
struct ResultType<M, L, R> {
using type = typename MathWrapper<M, L, R>::type;
};
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
struct ResultType {
using type =
typename ResultType<M, typename ResultType<M, L, R>::type, Args...>::type;
};
// The following macros are just boilerplate for the standard arithmetic
// operator overloads and variadic function templates. A macro isn't the nicest
// solution, but it beats rewriting these over and over again.
#define BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME) \
template <typename L, typename R, typename... Args> \
constexpr CLASS##Numeric< \
typename ResultType<CLASS##OP_NAME##Op, L, R, Args...>::type> \
CL_ABBR##OP_NAME(const L lhs, const R rhs, const Args... args) { \
return CL_ABBR##MathOp<CLASS##OP_NAME##Op, L, R, Args...>(lhs, rhs, \
args...); \
}
#define BASE_NUMERIC_ARITHMETIC_OPERATORS(CLASS, CL_ABBR, OP_NAME, OP, CMP_OP) \
/* Binary arithmetic operator for all CLASS##Numeric operations. */ \
template <typename L, typename R, \
typename std::enable_if<Is##CLASS##Op<L, R>::value>::type* = \
nullptr> \
constexpr CLASS##Numeric< \
typename MathWrapper<CLASS##OP_NAME##Op, L, R>::type> \
operator OP(const L lhs, const R rhs) { \
return decltype(lhs OP rhs)::template MathOp<CLASS##OP_NAME##Op>(lhs, \
rhs); \
} \
/* Assignment arithmetic operator implementation from CLASS##Numeric. */ \
template <typename L> \
template <typename R> \
constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP( \
const R rhs) { \
return MathOp<CLASS##OP_NAME##Op>(rhs); \
} \
/* Variadic arithmetic functions that return CLASS##Numeric. */ \
BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME)
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_

View File

@@ -1,13 +0,0 @@
{"Registrations":[
{
"component": {
"type": "git",
"git": {
"repositoryUrl": "https://github.com/chromium/chromium",
"commitHash": "d8710dd959da8e3be56f20af8cc94fbf560fbb6b"
}
}
}
],
"Version": 1
}

View File

@@ -7,31 +7,4 @@ This file contains notes about debugging various items in the repository.
If you want to debug code in the Cascadia package via Visual Studio, your breakpoints will not be hit by default. A tweak is required to the *CascadiaPackage* project in order to enable this.
1. Right-click on *CascadiaPackage* in Solution Explorer and select Properties.
2. Change the *Application process* type from *Mixed (Managed and Native)* to *Native Only*.
## Popping into the Debugger from Running Code
Sometimes you will encounter a scenario where you need to break into the console or terminal code under the debugger but you cannot, for whatever reason, do so by launching it from the beginning under the debugger. This can be especially useful for debugging tests with TAEF which usually launch through several child processes and modules before hitting your code.
To accomplish this, add a `DebugBreak()` statement somewhere in the code and ensure you have a Post-Mortem debugger set.
**NOTE:** `conhost.exe` already has a provision for a conditional `DebugBreak()` very early in the startup code if it was built in debug mode. Set `HKCU\Console` with `DebugLaunch` as a `REG_DWORD` with the value of `1`.
### Setting Visual Studio as Post Mortem Debugger
Go to `Tools > Options` and then make sure that `Native` is checked as the `Just-In-Time Debugging` provider. (Checking the box, if it is not checked, will require that Visual Studio is launched as Administrator.)
![image](https://user-images.githubusercontent.com/18221333/72091481-1b870100-32c5-11ea-8235-cebb9a383c32.png)
Then when you run something with `DebugBreak()` in it, you will see this:
![image](https://user-images.githubusercontent.com/18221333/72091543-42453780-32c5-11ea-8b4b-83a362eb73df.png)
The top ones will be new instances of the Visual Studios installed on your system. The bottom ones will be the running instances of Visual Studio. You can see in the image that one is open already. If you choose the bottom one, VS will attach straight up as if you F5'd from the solution at the point from the `DebugBreak()`. Step up to get out of the break and back into the code.
### Setting WinDBG as Post Mortem Debugger
From an elevated context (a command prompt or whatnot...), run `windbg /I`. This will install the debugger as Post Mortem.
Then run the thing and it will pop straight into a new WinDBG session. Step up to get out of the break and back into the code.
**Caveat:** If you are on an x64 system, you may need to do `windbg /I` with both the x64 and x86 versions of the debugger to catch all circumstances (like if you're trying to run x86 code.)
2. Change the *Application process* type from *Mixed (Managed and Native)* to *Native Only*.

View File

@@ -5,4 +5,3 @@
1. If it's brand new code or refactoring a complete class or area of the code, please follow as Modern C++ of a style as you can and reference the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) as much as you possibly can.
1. When working with any Win32 or NT API, please try to use the [Windows Implementation Library](./WIL.md) smart pointers and result handlers.
1. The use of NTSTATUS as a result code is discouraged, HRESULT or exceptions are preferred. Functions should not return a status code if they would always return a successful status code. Any function that returns a status code should be marked `noexcept` and have the `nodiscard` attribute.
1. When contributing code in `TerminalApp`, be mindful to appropriately use C++/WinRT [strong and weak references](https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/weak-references), and have a good understanding of C++/WinRT [concurrency schemes](https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/concurrency).

View File

@@ -67,8 +67,7 @@
"switchToTab6",
"switchToTab7",
"switchToTab8",
"toggleFullscreen",
"find"
"toggleFullscreen"
],
"type": "string"
},
@@ -84,8 +83,7 @@
"SplitState": {
"enum": [
"vertical",
"horizontal",
"auto"
"horizontal"
],
"type": "string"
},
@@ -215,8 +213,8 @@
"action": { "type": "string", "pattern": "splitPane" },
"split": {
"$ref": "#/definitions/SplitState",
"default": "auto",
"description": "The orientation to split the pane in, either vertical (think [|]), horizontal (think [-]), or auto (splits pane based on remaining space)"
"default": "vertical",
"description": "The orientation to split the pane in, either vertical (think [|]) or horizontal (think [-])"
}
}
}

View File

@@ -1,739 +0,0 @@
---
author: Mike Griese @zadjii-msft
created on: 2019-11-08
last updated: 2020-01-15
issue id: #607
---
# Commandline Arguments for the Windows Terminal
## Abstract
This spec outlines the changes necessary for Windows Terminal to support
commandline arguments. These arguments can be used to enable customized launch
scenarios for the Terminal, such as booting directly into a specific profile or
directory.
## Inspiration
Since the addition of the "execution alias" `wt.exe` which enables launching the
Windows Terminal from the commandline, we've always wanted to support arguments
to enable custom launch scenarios. This need was amplified by requests like:
* [#576], which wanted to add jumplist entries for the Windows Terminal, but was
blocked because there was no way of communicating to the Terminal _which_
profile it wanted to launch
* [#1060] - being able to right-click in explorer to "open a Windows Terminal
Here" is great, but would be more powerful if it could also provide options to
open specific profiles in that directory.
* [#2068] - We want the user to be able to (from inside the Terminal) not only
open a new window with the default profile, but also open the new window with
a specific profile.
Additionally, the final design for the arguments was heavily inspired by the
arguments available to `tmux`, which also enables robust startup configuration
through commandline arguments.
## User Stories
Lets consider some different ways that a user or developer might want want to
use commandline arguments, to help guide the design.
1. A user wants to open the Windows Terminal with their default profile.
- This one is easy, it's already provided with simply `wt`.
2. A user wants to open the Windows Terminal with a specific profile from their
list of profiles.
3. A user wants to open the Windows Terminal with their default profile, but
running a different commandline than usual.
4. A user wants to know the list of arguments supported by `wt.exe`.
5. A user wants to see their list of profiles, so they can open one in
particular
6. A user wants to open their settings file, without needing to open the
Terminal window.
7. A user wants to know what version of the Windows Terminal they are running,
without needing to open the Terminal window.
8. A user wants to open the Windows Terminal at a specific location on the
screen
9. A user wants to open the Windows Terminal in a specific directory.
10. A user wants to open the Windows Terminal with a specific size
11. A user wants to open the Windows Terminal with only the default settings,
ignoring their user settings.
12. A user wants to open the Windows Terminal with multiple tabs open
simultaneously, each with different profiles, starting directories, even
commandlines
13. A user wants to open the Windows Terminal with multiple tabs and panes open
simultaneously, each with different profiles, starting directories, even
commandlines, and specific split sizes
14. A user wants to use a file to provide a reusable startup configuration with
many steps, to avoid needing to type the commandline each time.
## Solution Design
### Proposal 1 - Parameters
Initially, I had considered arguments in the following style:
* `--help`: Display the help message
* `--version`: Display version info for the Windows Terminal
* `--list-profiles`: Display a list of the available profiles
- `--all` to also show "hidden" profiles
- `--verbose`? To also display GUIDs?
* `--open-settings`: Open the settings file
* `--profile <profile name>`: Start with the given profile, by name
* `--guid <profile guid>`: Start with the given profile, by GUID
* `--startingDirectory <path>`: Start in the given directory
* `--initialRows <rows>`, `--initialCols <rows>`: Start with a specific size
* `--initialPosition <x,y>`: Start at an initial location on the screen
* `-- <commandline>`: Start with this commandline instead
However, this style of arguments makes it very challenging to start multiple
tabs or panes simultaneously. How would a user start multiple panes, each with a
different commandline? As configurations become more complex, these commandlines
would quickly become hard to parse and understand for the user.
### Proposal 2 - Commands and Parameters
Instead, we'll try to seperate these arguments by their responsibilities. Some
of these arguments cause something to happen, like `help`, `version`, or
`open-settings`. Other arguments act more like modifiers, like for example
`--profile` or `--startingDirectory`, which provide additional information to
the action of _opening a new tab_. Lets try and define these concepts more
clearly.
**Commands** are arguments that cause something to happen. They're provided in
`kebab-case`, and can have some number of optional or required "parameters".
**Parameters** are arguments that provide additional information to "commands".
They can be provided in either a long form or a short form. In the long form,
they're provided in `--camelCase`, with two hyphens preceding the argument
name. In short form, they're provided as just a single character preceded by a
hyphen, like so: `-c`.
Let's enumerate some possible example commandlines, with explanations, to
demonstrate:
### Sample Commandlines
```sh
# Runs the user's "Windows Powershell" profile in a new tab (user story 2)
wt new-tab --profile "Windows Powershell"
wt --profile "Windows Powershell"
wt -p "Windows Powershell"
# Runs the user's default profile in a new tab, running cmd.exe (user story 3)
wt cmd.exe
# display the help text (user story 4)
wt help
wt --help
wt -h
wt -?
wt /?
# output the list of profiles (user story 5)
wt list-profiles
# open the settings file, without opening the Terminal window (user story 6)
wt open-settings
# Display version info for the Windows Terminal (user story 7)
wt version
wt --version
wt -v
# Start the default profile in directory "c:/Users/Foo/dev/MyProject" (user story 9)
wt new-tab --startingDirectory "c:/Users/Foo/dev/MyProject"
wt --startingDirectory "c:/Users/Foo/dev/MyProject"
wt -d "c:/Users/Foo/dev/MyProject"
# Windows-style paths work too
wt -d "c:\Users\Foo\dev\MyProject"
# Runs the user's "Windows Powershell" profile in a new tab in directory
# "c:/Users/Foo/dev/MyProject" (user story 2, 9)
wt new-tab --profile "Windows Powershell" --startingDirectory "c:/Users/Foo/dev/MyProject"
wt --profile "Windows Powershell" --startingDirectory "c:/Users/Foo/dev/MyProject"
wt -p "Windows Powershell" -d "c:/Users/Foo/dev/MyProject"
# open a new tab with the "Windows Powershell" profile, and another with the
# "cmd" profile (user story 12)
wt new-tab --profile "Windows Powershell" ; new-tab --profile "cmd"
wt --profile "Windows Powershell" ; new-tab --profile "cmd"
wt --profile "Windows Powershell" ; --profile "cmd"
wt --p "Windows Powershell" ; --p "cmd"
# run "my-commandline.exe with some args" in a new tab
wt new-tab my-commandline.exe with some args
wt my-commandline.exe with some args
# run "my-commandline.exe with some args and a ; literal semicolon" in a new
# tab, and in another tab, run "another.exe running in a second tab"
wt my-commandline.exe with some args and a \; literal semicolon ; new-tab another.exe running in a second tab
# Start cmd.exe, then split it vertically (with the first taking 70% of it's
# space, and the new pane taking 30%), and run wsl.exe in that pane (user story 13)
wt cmd.exe ; split-pane --target 0 -v -% 30 wsl.exe
wt cmd.exe ; split-pane -% 30 wsl.exe
# Create a new window with the default profile, create a vertical split with the
# default profile, then create a horizontal split in the second pane and run
# "media.exe" (user story 13)
wt new-tab ; split-pane -v ; split-pane --target 1 -h media.exe
wt new-tab ; split-pane -v ; split-pane -t 1 -h media.exe
```
## `wt` Syntax
The `wt` commandline is divided into two main sections: "Options", and "Commands":
`wt [options] [command ; ]...`
Options are a list of flags and other parameters that can control the behavior
of the `wt` commandline as a whole. Commands are a semicolon-delimited list of
commands and arguments for those commands.
If no command is specified in a `command`, then the command is assumed to be a
`new-tab` command by default. So, for example, `wt cmd.exe` is interpreted the
same as `wt new-tab cmd.exe`.
To take this a step further, empty commands surrounded by semicolons will also
be interpreted as `new-tab` commands with the default parameters, so `wt ; ; ;`
can be used to open the windows terminal with **4** new tabs. Effectively, that
commandline expands to `wt new-tab ; new-tab ; new-tab ; new-tab`.
<!--
### Aside: What should the default command be?
These are notes from my draft intentionally left here to help understand the
conclusion that new-tab should be the default command.
Should the default command be `new-window` or `new-tab`?
`new-window` makes sense to take params like `--initialPosition`,
`--initialRows`/`--initialCols`, and _implies_ `new-tab`. However, chained
commands that want to open in the same window _need_ to specify `new-tab`,
otherwise they'll all appear in new windows.
If it's `new-tab`, then how do `--initialRows` (etc) work? `new-tab` generally
_doesn't_ accept those parameters, because it's going to be inheriting the
parent's window size. Do we just ignore them for subsequent invocations? I
suppose that makes sense, once the first tab has set those, then the other tabs
can't really change them.
When dealing with a file full of startup commands, we'll assume all of them are
intended for the given window. So the first `new-tab` in the file will create
the window, and all subsequent `new-tab` commands will create tabs in that same
window.
-->
### Options
#### `--help,-h,-?,/?,`
Runs the `help` command.
#### `--version,-v`
Runs the `version` command.
#### `--session,-s session-id`
Run these commands in the given Windows Terminal session. Enables opening new
tabs in already running Windows Terminal windows. This feature is dependent upon
other planned work landing, so is only provided as an example, of what it might
look like. See [Future Considerations](#Future-Considerations) for more details.
#### `--file,-f configuration-file`
Run these commands in the given Windows Terminal session. Enables opening new
tabs in already running Windows Terminal windows. See [Future
Considerations](#Future-Considerations) for more details.
### Commands
#### `help`
`help`
Display the help message.
#### `version`
`version`
Display version info for the Windows Terminal.
#### `open-settings`
`open-settings [--defaults,-d]`
Open the settings file. If this command is provided alone, it does not open the
terminal window.
**Parameters**:
* `--defaults,-d`: Open the `defaults.json` file instead of the `profiles.json`
file.
#### `list-profiles`
`list-profiles [--all,-A] [--showGuids,-g]`
Displays a list of each of the available profiles. Each profile displays it's
name, seperated by newlines.
**Parameters**:
* `--all,-A`: Show all profiles, including profiles marked `"hidden": true`.
* `--showGuids,-g`: In addition to showing names, also list each profile's
guid. These GUIDs should probably be listed _first_ on each line, to make
parsing output easier.
#### `new-tab`
`new-tab [--initialPosition x,y]|[--maximized]|[--fullscreen] [--initialRows rows] [--initialCols cols] [terminal_parameters]`
Opens a new tab with the given customizations. On its _first_ invocation, also
opens a new window. Subsequent `new-tab` commands will all open new tabs in the
same window.
**Parameters**:
* `--initialPosition x,y`: Create the new Windows Terminal window at the given
location on the screen in pixels. This parameter is only used when initially
creating the window, and ignored for subsequent `new-tab` commands. When
combined with any of `--maximized` or `--fullscreen`, an error message will be
displayed to the user, indicating that an invalid combination of arguments was
provided.
* `--initialRows rows`: Create the terminal window with `rows` rows (in
characters). If omitted, uses the value from the user's settings. This
parameter is only used when initially creating the window, and ignored for
subsequent `new-tab` commands. When combined with any of `--maximized` or
`--fullscreen`, an error message will be displayed to the user, indicating
that an invalid combination of arguments was provided.
* `--initialCols cols`: Create the terminal window with `cols` cols (in
characters). If omitted, uses the value from the user's settings. This
parameter is only used when initially creating the window, and ignored for
subsequent `new-tab` commands. When combined with any of `--maximized` or
`--fullscreen`, an error message will be displayed to the user, indicating
that an invalid combination of arguments was provided.
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `split-pane`
`split-pane [--target,-t target-pane] [-h]|[-v] [--percent,-% split-percentage] [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
larger dimension is. If both `-h` and `-v` are provided, defaults to vertical.
* `--percent,-% split-percentage`: Designates the amount of space that the new
pane should take as a percentage of the parent's space. If omitted, the pane
will take 50% by default.
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `focus-tab`
`focus-tab [--target,-t tab-index]`
Moves focus to a given tab.
**Parameters**:
* `--target,-t tab-index`: moves focus to the tab at index `tab-index`. If omitted,
defaults to `0` (the first tab).
#### `focus-pane`
`focus-pane [--target,-t target-pane]`
Moves focus within the currently focused tab to a given pane.
**Parameters**:
* `--target,-t target-pane`: moves focus to 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 (which is effectively a
no-op).
#### `move-focus`
`move-focus [--direction,-d direction]`
Moves focus within the currently focused tab in the given direction.
**Parameters**:
* `--direction,-d direction`: moves focus in the given `direction`. `direction`
should be one of [`left`, `right`, `up`, `down`]. If omitted, does not move
the focus at all (resulting in a no-op).
#### `[terminal_parameters]`
Some of the preceding commands are used to create a new terminal instance.
These commands are listed above as accepting `[terminal_parameters]` as a
parameter. For these commands, `[terminal_parameters]` can be any of the
following:
`[--profile,-p profile-name] [--startingDirectory,-d starting-directory] [commandline]`
* `--profile,-p profile-name`: Use the given profile to open the new tab/pane,
where `profile-name` is the `name` or `guid` of a profile. If `profile-name`
does not match _any_ profiles, uses the default.
* `--startingDirectory,-d starting-directory`: Overrides the value of
`startingDirectory` of the specified profile, to start in `starting-directory`
instead.
* `commandline`: A commandline to replace the default commandline of the
selected profile. If the user wants to use a `;` in this commandline, it
should be escaped as `\;`.
Fundamentally, there's no reason that _all_ the current profile settings
couldn't be overridden by commandline arguments. Practically, it might be
unreasonable to create short form arguments for each and every Profile
property, but the long form would certainly be reasonable.
The arguments listed above represent both special cases of the profile settings
like `guid` and `name`, as well as high priority properties to add as arguments.
* It doesn't really make sense to override `name` or `guid`, so those have been
repurposed as arguments for selecting a profile.
* `commandline` is a bit of a unique case - we're not explicitly using an
argument to identify the start of the commandline here. This is to help avoid
the need to parse and escape arguments to the client commandline.
* `startingDirectory` is a _highly_ requested commandline argument, so that's
been given priority in this spec.
## Implementation Details
Following an investigation performed the week of Nov 18th, 2019, I've determined
that we should be able to use the [CLI11] open-source library to parse
our arguments. We'll need to add some additional logic on top of CLI11 in order
to properly seperate commands with `;`, but that's not impossible to achieve.
CLI11 will allow us to parse commandlines as a series of options, with a
possible sub-command that takes its own set of parameters. This functionality
will be used to enable our options & commands style of parameters.
When commands are parsed, each command will build an `ActionAndArgs` that can be
used to tell the terminal what steps to perform on startup. The Terminal already
uses these `ActionAndArgs` to perform actions like opening new tabs, panes,
moving focus, etc.
In my initial investigation, it seemed as though the Terminal did not initialize
the size of child controls initially. This meant that it wasn't possible to
immediately create all the splits and tabs for the Terminal as passed on the
commandline, because they'd open at a size of 0x0. To mitigate this, we'll
handle dispatching these startup actions one at a time, waiting until the
Terminal for an action is initialized or the command is otherwise completed
before dispatching the next one.
This is a perhaps fragile way of handling the initialization. Ideally, there
should be a way to dispatch all the commands _immediately_, before the Terminal
fully initializes, so that the UI pops up in the state as specified in the
commandline. This will be an area of active investigation as implementation is
developed, to make the initialization of many commands as seamless as possible.
### Implementation plan
As this is a very complex feature, there will need to be a number of steps taken
in the codebase to enable this functionality in a way that users are expecting.
The following is a suggestion of the individual changelists that could be made
to iteratively work towards fulling implementing this funcionality.
* [x] Refactor `ShortcutAction` dispatching into its own class
- Right now, the `AppKeyBindings` is responsible for triggering all
`ActionAndArgs` events, but only based upon keystrokes while the Terminal is
running. As we'll be re-using `ActionAndArgs` for handling startup events,
we'll need a more generic way of dispatching those events.
* [x] Add a `SplitPane` `ShortcutAction`, with a single parameter `split`,
which accepts either `vertical`, `horizontal`, or `auto`.
- Make sure to convert the legacy `SplitVertical` and `SplitHorizontal` to use
`SplitPane` with that arg set appropriately.
* [x] Add a `TerminalParameters` winrt object to `NewTabArgs` and `SplitPane`
args. `TerminalParameters` will include the following properties:
```c#
runtimeclass TerminalParameters {
String ProfileName;
String ProfileGuid;
String StartingDirectory;
String Commandline;
}
```
- These represent the arguments in `[terminal_parameters]`. When set, they'll
both `newTab` and `splitPane` will accept [`profile`, `guid`, `commandline`,
`startingDirectory`] as optional parameters, and when they're set, they'll
override the default values used when creating a new terminal instance.
- `profile` and `guid` will be used to look up the profile to create by
`name`, `guid`, respectively, as opposed to the default profile.
- The others will override their respective properties from the
`TerminalSettings` created for that profile.
* [x] Add an optional `"percent"` argument to `SplitPane`, that enables a pane
to be split with a specified percent of the parent pane.
* [x] Add support to `TerminalApp` for parsing commandline arguments, and
constructing a list of `ActionAndArgs` based on those commands.
- This will include adding tests that validate a particular commandline
generates the given sequence of `ActionAndArgs`.
- This will _not_ include _performing_ those actions, or passing the
commandline from the `WindowsTerminal` executable to the `TerminalApp`
library for parsing. This change does not add any user-facing functional
behavior, but is self-contained enough that it can be its own changelist,
without depending upon other functionality.
* [ ] When parsing a `new-tab` command, configure the `TerminalApp::AppLogic` to
set some initial state about itself, to handle the `new-tab` arguments
[`--initialPosition`, `--maximized`, `--initialRows`, `--initialCols`]. Only
set this state for the first `new-tab` parsed. These settings will overwrite
the corresponding global properties on launch.
* [ ] When parsing a `help` command or a `list-profiles` command, trigger a
event on `AppLogic`. This event should be able to be handled by
WindowsTerminal (`AppHost`), and used to display a `MessageBox` with the given
text. (see [Potential Issues](##subsystemwindows-or-subsystemconsole) for a
discussion on this).
* [ ] Add support for performing actions passed on the commandline. This
includes:
- Passing the commandline into the `TerminalApp` for parsing.
- Performing `ActionAndArgs` that are parsed by the Terminal.
- At this point, the user should be able to pass the following commands to the
Terminal:
- `new-tab`
- `split-pane`
- `move-focus`
- `focus-tab`
- `open-settings`
- `help`
- `list-profiles`
* [ ] Add a `ShortcutAction` for `FocusPane`, which accepts a single parameter
`index`.
- We'll need to track each `Pane`'s ID as `Pane`s are created, so that we can
quicky switch to the i'th `Pane`.
- This is in order to support the `-t,--target` parameter of `split-pane`.
## Capabilities
### Accessibility
As a commandline feature, the accessibility of this feature will largely be tied
to the ability of the commandline environment to expose accessibility
notifications. Both `conhost.exe` and the Windows Terminal already support
basic accessibility patterns, so users using this feature from either of those
terminals will be reliant upon their accessibility implementations.
### Security
As we'll be parsing user input, that's always subject to worries about buffer
length, input values, etc. Fortunately, most of this should be handled for us by
the operating system, and passed to us as a commandline via `winMain` and
`CommandLineToArgvW`. We should still take extra care in parsing these args.
### Reliability
This change should not have any particular reliability concerns.
### Compatibility
This change should not regress any existing behaviors.
### Performance, Power, and Efficiency
This change should not particularily impact startup time or any of these other categories.
## Potential Issues
### Commandline escaping
Escaping commandlines is notoriously tricky to do correctly. Since we're using
`;` to delimit commands, which might want to also use `;` in the commandline
itself, we'll use `\;` as an escaped `;` within the commandline. This is an area
we've been caught in before, so extensive testing will be necessary to make sure
this works as expected.
Painfully, powershell uses `;` as a seperator between commands as well. So, if
someone wanted to call a `wt` commandline in powershell with multiple commands,
the user would need to also escape those semicolons for powershell first. That
means a command like ```wt new-tab ; split-pane``` would need to be ```wt new-tab
`; split-pane``` in powershell, and ```wt new-tab ; split-pane commandline \; with
\; semicolons``` would need to become ```wt new-tab `; split-pane commandline \`;
with \`; semicolons```, using ```\`;``` to first escape the semicolon for
powershell, then the backslash to escape it for `wt`.
Alternatively, the user could choose to escape the semicolons with quotes
(either single or double), like so: ```wt new-tab ';' split-pane "commandline \;
with \; semicolons"```.
This would get a little ridiculous when using powershell commands that also have
semicolons possible escaped within them:
```powershell
wt.exe ";" split-pane "powershell Write-Output 'Hello World' > foo.txt; type foo.txt"
```
We've decided that although this behavior is uncomfortable in powershell, there
doesn't seem to be any option out there that's _less_ painful. This is a
reasonable option that makes enough logical sense. Users familiar with
powershell will understand the need to escape commandlines like this.
As noted by @jantari:
> PowerShell has the --% (stop parsing) operator, which instructs it to stop
> interpreting anything after it and just pass it on verbatim. So, the
> semicolon-problem could also be addressed by the following syntax:
> ```sh
> # wt.exe still needs to be interpreted by PowerShell as it's a command in PATH, but nothing after it
> wt.exe --% cmd.exe ; split-pane --target-pane 0 -v -% 30 wsl.exe
> ```
### `/SUBSYSTEM:Windows` or `/SUBSYSTEM:Console`?
When you create an application on Windows, you must link it as either a Windows
or a Console application. When the application is launched from a commandline
shell as a Windows application, the shell will immediately return to the
foreground of the console, which means that any console output emitted by the
process will be intermixed with the shell. However, if an application is linked
as a Console application, and it's launched from the Start Menu, Run dialog, or
any other context that's _not_ a console, then the OS will _automatically_
create a console to host the commandline application. That means that briefly, a
console window will appear on the screen, even if we decide that we just want to
launch our application's window.
This basically leaves us with two bad scenarios. Either we're a Console
application, and a console window always flashes on screen for every
non-commandline invocation of the Terminal, or we're a Windows application, and
console output we log (including help messages) can get mixed with shell output.
Neither of these are particularly good.
`python` et. al. often ship with _two_ executables, a `python.exe` which is a
Console application, and a `pythonw.exe`, which is a Windows application. This
however has led to [loads of confusion](https://stackoverflow.com/a/30313091),
and even with plentiful documentation, would likely result in users being
confused about what does what. For situations like launching the Terminal in the
CWD of `explorer.exe`, users would need to use `wtw.exe -d .` to prevent the
console window from appearing. However, when calling Windows Terminal from a
commandline environment, users who call `wtw.exe /?` would likely get unexpected
behavior, because they should have instead called `wt.exe /?`.
To avoid this confusion, I propose we follow the example of `msiexec /?`. This
is a Windows application that uses a `MessageBox` to display its help text.
While this is less convenient for users coming exclusively from a commandline
environment, it's also the least bad option available to us.
* It's less confusing than having control returned to the shell
* It's not as bad as forcing the creation of a console window for
non-commandline launches.
* There's precedent for this kind of dialog (we're not inventing a new pattern
here).
### What happens if `new-tab` isn't the first command?
Consider the following commandline:
```sh
wt.exe split-pane -v ; new-tab
```
In the future, maybe we could presume in this case that the commands are
intended for the current Windows Terminal window, though that's not
functionality that will arrive in 1.0. Even when sessions are supported like
that, I'm not sure that when we're parsing a commandline, we'll be able to
know what session we're currently running in. That might make it challenging to
dispatch this kind of command to "the current WT window".
Additionally, what would happen if this was run in a `conhost` window, that
wasn't attached to a Terminal session? We wouldn't be able to tell _the current
session_ to `split-pane`, since there wouldn't be one. What would we do then?
Display an error message somehow?
I don't believe that implying the _current Windows Terminal session_ is the
correct behavior here. Instead we should either:
* Assume that there's an implicit `new-tab` command that's run first, to create
the window, _then_ run `split-pane` in that tab.
* Immediately display an error that the commandline is invalid, and that a
commandline should start with a `new-tab ; `?
In my initial implementation, I resolved this by assuming there was an implicit
`new-tab` command, and that felt right. The team has discussed this, and
concluded that's the correct behavior. In the words of @DHowett-MSFT:
> In favor of "implicit `new-tab`": `wt.exe` without any arguments is _already_
> an implicit `new-window` or `new-tab`; we can't claw back the implicitness and
> ease of use in that one, so I think in the spirit of keeping that going WT
> should automatically do anything necessary to service a command (`wt
> split-pane` should operate in a new tab or new window, etc.)
We should also make sure that when we add support for the `open-settings`
command, that command by itself should not imply a `new-tab`. `wt open-settings`
should simply open the settings in the user's chosen `.json` editor, without
needing to open a terminal window.
## Future considerations
* These are some additional argument ideas which are dependent on other features
that might not land for a long time. These features were still considered as a
part of the design of this solution, though their implementation is purely
hypothetical for the time being.
* Instead of launching a new Windows Terminal window, attach this new
terminal to an existing one. This would require the work outlined in
[#2080], so support a "manager" process that could coordinate sessions
like this.
- This would be something like `wt --session [some-session-id]
[commands]`, where `--session [some-session-id]` would tell us that
`[more-commands]` are intended for the given other session/window.
That way, you could open a new tab in another window with `wt --session
0 cmd.exe` (for example).
* `list-sessions`: A command to display all the active Windows terminal
instances and their session ID's, in a way compatible with the above
command. Again, heavily dependent upon the implementation of [#2080].
* `--elevated`: Should it be possible for us to request an elevated session
of ourselves, this argument could be used to indicate the process should
launch in an _elevated_ context. This is considered in pursuit of [#632].
* `--file,-f configuration-file`: Used for loading a configuration file to
give a list of commands. This file can enable a user to have a re-usable
configuration saved somewhere on their machine. When dealing with a file
full of startup commands, we'll assume all of them are intended for the
given window. So the first `new-tab` in the file will create the window,
and all subsequent `new-tab` commands will create tabs in that same
window.
* In the past we've had requests (like [#756]) for having the terminal start
with multiple tabs/panes by default. This might be a path to enabling that
scenario. One could imagine the `profiles.json` file including a
`defaultConfiguration` property, with a path to a .conf file filled with
commands. We'd parse that file on window creation just the same as if it was
parsed on the commandline. If the user provides a file on the commandline,
we'll just ignore that value from `profiles.json`.
* When working on "New Window", we'll want the user to be able to open a new
window with not only the default profile, but also a specific profile. This
will help us enable that scenario.
* We might want to look into `RegisterArgumentCompleter` in powershell to
enable letting the user auto-complete our args in powershell.
* If we're careful, we could maybe create short form aliases for all the
commands, so the user wouldn't need to type them all out every time. `new-tab`
could become `nt`, `split-pane` becomes `sp`, etc. A commandline could look
like `wt ; sp less some-log.txt ; fp -t 0` then.
## Resources
Feature Request: wt.exe supports command line arguments (profile, command, directory, etc.) [#607]
Add "open Windows terminal here" into right-click context menu [#1060]
Feature Request: Task Bar jumplist should show items from profile [#576]
Draft spec for adding profiles to the Windows jumplist [#1357]
Spec for tab tear off and default app [#2080]
[Question] Configuring Windows Terminal profile to always launch elevated [#632]
New window key binding not working [#2068]
Feature Request: Start with multiple tabs open [#756]
<!-- Footnotes -->
[#756]: https://github.com/microsoft/terminal/issues/756
[#576]: https://github.com/microsoft/terminal/issues/576
[#607]: https://github.com/microsoft/terminal/issues/607
[#632]: https://github.com/microsoft/terminal/issues/632
[#1060]: https://github.com/microsoft/terminal/issues/1060
[#1357]: https://github.com/microsoft/terminal/pull/1357
[#2068]: https://github.com/microsoft/terminal/issues/2068
[#2080]: https://github.com/microsoft/terminal/pull/2080
[CLI11]: https://github.com/CLIUtils/CLI11

View File

@@ -51,19 +51,6 @@ Note that the starting directory of Cygwin is set as it is to make the path
work. The default directory opened when starting Cygwin will be `$HOME` because
of the `--login` flag.
## Far Manager
Assuming that you've installed Far into `c:\Program Files\Far Manager`:
```json
{
"name" : "Far",
"commandline" : "\"c:\\program files\\far manager\\far.exe\"",
"startingDirectory" : "%USERPROFILE%",
"useAcrylic" : false
},
```
## Git Bash
Assuming that you've installed Git Bash into `C:/Program Files/Git`:

View File

@@ -1545,225 +1545,3 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
return {};
}
}
// Function Description:
// - Reflow the contents from the old buffer into the new buffer. The new buffer
// can have different dimensions than the old buffer. If it does, then this
// function will attempt to maintain the logical contents of the old buffer,
// by continuing wrapped lines onto the next line in the new buffer.
// Arguments:
// - oldBuffer - the text buffer to copy the contents FROM
// - newBuffer - the text buffer to copy the contents TO
// 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)
{
Cursor& oldCursor = oldBuffer.GetCursor();
Cursor& newCursor = newBuffer.GetCursor();
// skip any drawing updates that might occur as we manipulate the new buffer
oldCursor.StartDeferDrawing();
newCursor.StartDeferDrawing();
// We need to save the old cursor position so that we can
// place the new cursor back on the equivalent character in
// the new buffer.
const COORD cOldCursorPos = oldCursor.GetPosition();
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter();
short const cOldRowsTotal = cOldLastChar.Y + 1;
short const cOldColsTotal = oldBuffer.GetSize().Width();
COORD cNewCursorPos = { 0 };
bool fFoundCursorPos = 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++)
{
// Fetch the row and its "right" which is the last printable character.
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
const CharRow& charRow = row.GetCharRow();
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
// There is a special case here. If the row has a "wrap"
// flag on it, but the right isn't equal to the width (one
// index past the final valid index in the row) then there
// were a bunch trailing of spaces in the row.
// (But the measuring functions for each row Left/Right do
// not count spaces as "displayable" so they're not
// included.)
// As such, adjust the "right" to be the width of the row
// to capture all these spaces
if (charRow.WasWrapForced())
{
iRight = cOldColsTotal;
// And a combined special case.
// If we wrapped off the end of the row by adding a
// piece of padding because of a double byte LEADING
// character, then remove one from the "right" to
// leave this padding out of the copy process.
if (charRow.WasDoubleBytePadded())
{
iRight--;
}
}
// Loop through every character in the current row (up to
// the "right" boundary, which is one past the final valid
// character)
for (short iOldCol = 0; iOldCol < iRight; iOldCol++)
{
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
}
try
{
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto glyph = row.GetCharRow().GlyphAt(iOldCol);
const auto dbcsAttr = row.GetCharRow().DbcsAttrAt(iOldCol);
const auto textAttr = row.GetAttrRow().GetAttrByColumn(iOldCol);
if (!newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr))
{
hr = E_OUTOFMEMORY;
break;
}
}
CATCH_RETURN();
}
if (SUCCEEDED(hr))
{
// If we didn't have a full row to copy, insert a new
// line into the new buffer.
// Only do so if we were not forced to wrap. If we did
// force a word wrap, then the existing line break was
// only because we ran out of space.
if (iRight < cOldColsTotal && !charRow.WasWrapForced())
{
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
}
// Only do this if it's not the final line in the buffer.
// On the final line, we want the cursor to sit
// where it is done printing for the cursor
// adjustment to follow.
if (iOldRow < cOldRowsTotal - 1)
{
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
}
else
{
// If we are on the final line of the buffer, we have one more check.
// We got into this code path because we are at the right most column of a row in the old buffer
// that had a hard return (no wrap was forced).
// However, as we're inserting, the old row might have just barely fit into the new buffer and
// caused a new soft return (wrap was forced) putting the cursor at x=0 on the line just below.
// We need to preserve the memory of the hard return at this point by inserting one additional
// hard newline, otherwise we've lost that information.
// We only do this when the cursor has just barely poured over onto the next line so the hard return
// isn't covered by the soft one.
// e.g.
// The old line was:
// |aaaaaaaaaaaaaaaaaaa | with no wrap which means there was a newline after that final a.
// The cursor was here ^
// And the new line will be:
// |aaaaaaaaaaaaaaaaaaa| and show a wrap at the end
// | |
// ^ and the cursor is now there.
// If we leave it like this, we've lost the newline information.
// So we insert one more newline so a continued reflow of this buffer by resizing larger will
// continue to look as the original output intended with the newline data.
// After this fix, it looks like this:
// |aaaaaaaaaaaaaaaaaaa| no wrap at the end (preserved hard newline)
// | |
// ^ and the cursor is now here.
const COORD coordNewCursor = newCursor.GetPosition();
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
{
if (newBuffer.GetRowByOffset(gsl::narrow_cast<size_t>(coordNewCursor.Y) - 1).GetCharRow().WasWrapForced())
{
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
}
}
}
}
}
}
if (SUCCEEDED(hr))
{
// Finish copying remaining parameters from the old text buffer to the new one
newBuffer.CopyProperties(oldBuffer);
// If we found where to put the cursor while placing characters into the buffer,
// just put the cursor there. Otherwise we have to advance manually.
if (fFoundCursorPos)
{
newCursor.SetPosition(cNewCursorPos);
}
else
{
// Advance the cursor to the same offset as before
// get the number of newlines and spaces between the old end of text and the old cursor,
// then advance that many newlines and chars
int iNewlines = cOldCursorPos.Y - cOldLastChar.Y;
const int iIncrements = cOldCursorPos.X - cOldLastChar.X;
const COORD cNewLastChar = newBuffer.GetLastNonSpaceCharacter();
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
// because the cursor is already on the next line
if (newBuffer.GetRowByOffset(cNewLastChar.Y).GetCharRow().WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
else
{
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
// old buffer will be one more than in this buffer, so new need one LESS.
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).GetCharRow().WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
}
for (int r = 0; r < iNewlines; r++)
{
if (!newBuffer.NewlineCursor())
{
hr = E_OUTOFMEMORY;
break;
}
}
if (SUCCEEDED(hr))
{
for (int c = 0; c < iIncrements - 1; c++)
{
if (!newBuffer.IncrementCursor())
{
hr = E_OUTOFMEMORY;
break;
}
}
}
}
}
if (SUCCEEDED(hr))
{
// Save old cursor size before we delete it
ULONG const ulSize = oldCursor.GetSize();
// Set size back to real size as it will be taking over the rendering duties.
newCursor.SetSize(ulSize);
}
newCursor.EndDeferDrawing();
oldCursor.EndDeferDrawing();
return hr;
}

View File

@@ -158,8 +158,6 @@ public:
const std::wstring_view fontFaceName,
const COLORREF backgroundColor);
static HRESULT Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer);
private:
std::deque<ROW> _storage;
Cursor _cursor;

View File

@@ -29,7 +29,7 @@ namespace TerminalAppLocalTests
// details on that.
BEGIN_TEST_CLASS(ColorSchemeTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TerminalApp.LocalTests.AppxManifest.xml")
END_TEST_CLASS()
TEST_METHOD(CanLayerColorScheme);

View File

@@ -32,7 +32,7 @@ namespace TerminalAppLocalTests
// details on that.
BEGIN_TEST_CLASS(KeyBindingsTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TerminalApp.LocalTests.AppxManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ManyKeysSameAction);

View File

@@ -29,7 +29,7 @@ namespace TerminalAppLocalTests
// details on that.
BEGIN_TEST_CLASS(ProfileTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TerminalApp.LocalTests.AppxManifest.xml")
END_TEST_CLASS()
TEST_METHOD(CanLayerProfile);

View File

@@ -34,7 +34,7 @@ namespace TerminalAppLocalTests
// details on that.
BEGIN_TEST_CLASS(SettingsTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TerminalApp.LocalTests.AppxManifest.xml")
END_TEST_CLASS()
TEST_METHOD(TryCreateWinRTType);

View File

@@ -3,13 +3,9 @@
#include "pch.h"
#include "../TerminalApp/TerminalPage.h"
#include "../TerminalApp/MinMaxCloseControl.h"
#include "../TerminalApp/TabRowControl.h"
#include "../TerminalApp/ShortcutActionDispatch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/Tab.h"
#include "../CppWinrtTailored.h"
#include "JsonTestClass.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
@@ -23,7 +19,7 @@ namespace TerminalAppLocalTests
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class TabTests : public JsonTestClass
class TabTests
{
// For this set of tests, we need to activate some XAML content. For
// release builds, the application runs as a centennial application,
@@ -38,26 +34,18 @@ namespace TerminalAppLocalTests
BEGIN_TEST_CLASS(TabTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TerminalApp.LocalTests.AppxManifest.xml")
TEST_CLASS_PROPERTY(L"UAP:Host", L"Xaml")
TEST_CLASS_PROPERTY(L"UAP:WaitForXamlWindowActivation", L"true")
END_TEST_CLASS()
// These four tests act as canary tests. If one of them fails, then they
// can help you identify if something much lower in the stack has
// failed.
TEST_METHOD(EnsureTestsActivate);
TEST_METHOD(TryCreateSettingsType);
TEST_METHOD(TryCreateConnectionType);
TEST_METHOD(TryCreateLocalWinRTType);
TEST_METHOD(TryCreateXamlObjects);
TEST_METHOD(TryCreateTab);
TEST_METHOD(CreateSimpleTerminalXamlType);
TEST_METHOD(CreateTerminalMuxXamlType);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
};
void TabTests::EnsureTestsActivate()
@@ -68,7 +56,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_TRUE(true);
}
void TabTests::TryCreateSettingsType()
void TabTests::TryCreateLocalWinRTType()
{
// Verify we can create a WinRT type we authored
// Just creating it is enough to know that everything is working.
@@ -80,17 +68,6 @@ namespace TerminalAppLocalTests
VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize);
}
void TabTests::TryCreateConnectionType()
{
// Verify we can create a WinRT type we authored
// Just creating it is enough to know that everything is working.
winrt::Microsoft::Terminal::TerminalConnection::EchoConnection conn{};
VERIFY_IS_NOT_NULL(conn);
// We're doing this test seperately from the TryCreateSettingsType test,
// to ensure both dependent binaries (TemrinalSettings and
// TerminalConnection) both work individually.
}
void TabTests::TryCreateXamlObjects()
{
auto result = RunOnUIThread([]() {
@@ -142,26 +119,4 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::CreateSimpleTerminalXamlType()
{
winrt::com_ptr<winrt::TerminalApp::implementation::MinMaxCloseControl> mmcc{ nullptr };
auto result = RunOnUIThread([&mmcc]() {
mmcc = winrt::make_self<winrt::TerminalApp::implementation::MinMaxCloseControl>();
VERIFY_IS_NOT_NULL(mmcc);
});
VERIFY_SUCCEEDED(result);
}
void TabTests::CreateTerminalMuxXamlType()
{
winrt::com_ptr<winrt::TerminalApp::implementation::TabRowControl> tabRowControl{ nullptr };
auto result = RunOnUIThread([&tabRowControl]() {
tabRowControl = winrt::make_self<winrt::TerminalApp::implementation::TabRowControl>();
VERIFY_IS_NOT_NULL(tabRowControl);
});
VERIFY_SUCCEEDED(result);
}
}

View File

@@ -1,16 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- A note about this project: We're building the test code dll from this
project, but it _MUST_ be run in conjunction with the TestHostApp project.
TestHostApp actually will build a TestHost executable and packaging bits
that we can use to run our tests. We need TestHostApp so that our
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into it's own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
<ProjectGuid>{CA5CAD1A-b11c-4ddb-a4fe-c3afae9b5506}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
@@ -112,4 +101,85 @@
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<!-- This project will generate individual sxs manifests for each of our winrt libraries -->
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
<!-- This is important: actually add the _LocalTestsGenerateCombinedManifests
target to the list of targets to run. -->
<PropertyGroup>
<BeforeLinkTargets Condition="'$(WindowsTargetPlatformVersion)' &gt;= '10.0.18362.0'">
$(BeforeLinkTargets);
_LocalTestsGenerateCombinedManifests;
_LocalTestsBuildAppxManifest;
_LocalTestsCopyDependencies;
</BeforeLinkTargets>
</PropertyGroup>
<!-- Step 1: Combine all our SxS manifests into a single SxS manifest. TAEF
needs us to specify a single activation context at runtime, so we need a
single file with all our types in it.-->
<Target Name="_LocalTestsGenerateCombinedManifests"
Inputs="@(_ConsoleWinmdManifest)"
Outputs="$(OutDir)$(TargetName).manifest"
DependsOnTargets="_ConsoleGenerateAdditionalWinmdManifests">
<Exec Command="mt.exe -manifest @(_ConsoleWinmdManifest, ' -manifest ') -out:$(OutDir)$(TargetName).manifest" />
</Target>
<!-- Step 2: Take our combined SxS manifest, and use it to build an
Appxmanifest.xml. We'll use the Appxmanifest.prototype.xml in this project's
directory as a base, and the script will tak all our activatableClasses and
turn them into appxmanifest-compatible Extensions -->
<Target Name="_LocalTestsBuildAppxManifest"
Inputs="$(OutDir)$(TargetName).manifest"
Outputs="$(OutDir)$(TargetName).AppxManifest.xml"
DependsOnTargets="_LocalTestsGenerateCombinedManifests">
<Exec Command="powershell.exe -noprofile ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateAppxFromManifest.ps1 -SxSManifest $(OutDir)$(TargetName).manifest -AppxManifestPrototype $(TargetName).AppxManifest.prototype.xml -OutPath $(OutDir)$(TargetName).AppxManifest.xml" />
</Target>
<!-- Step 3: Manually copy all our dependent DLLs into our OutDir. For SxS
activation to work, they all need to be in the same directory as our test dll.
This is using code that's heavliy cribbed from WindowsTerminal.vcxproj, which
is already cribbed from the GetPackagingOutputs in
Microsoft.*.AppxPackage.targets. We're filtering this list down to the dlls,
pris and xbfs, because this list _can_ contain directories, which will make
the Copy task explode. -->
<PropertyGroup>
<_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true</_ContinueOnError>
<_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false</_ContinueOnError>
</PropertyGroup>
<!-- First gather the files... -->
<Target Name="MyGetPackagingOutputs" Returns="@(MyPackagingOutputs)">
<MSBuild
Projects="@(ProjectReferenceWithConfiguration)"
Targets="GetPackagingOutputs"
BuildInParallel="$(BuildInParallel)"
Properties="%(ProjectReferenceWithConfiguration.SetConfiguration); %(ProjectReferenceWithConfiguration.SetPlatform)"
Condition="'@(ProjectReferenceWithConfiguration)' != ''
and '%(ProjectReferenceWithConfiguration.BuildReference)' == 'true'
and '%(ProjectReferenceWithConfiguration.ReferenceOutputAssembly)' == 'true'"
ContinueOnError="$(_ContinueOnError)">
<Output TaskParameter="TargetOutputs" ItemName="_PackagingOutputsFromOtherProjects"/>
</MSBuild>
<ItemGroup>
<!-- IMPORTANT: Grab all the xbf files _except_ App.xbf. If that's there, then the tests will hyperexplode. -->
<MyPackagingOutputs Include="@(_PackagingOutputsFromOtherProjects)" Condition="'%(Extension)'=='.dll' Or '%(Extension)'=='.pri' Or ('%(Extension)'=='.xbf' And '%(Filename)' != 'App')" />
</ItemGroup>
</Target>
<!-- Then copy the files to our outdir -->
<Target Name="_LocalTestsCopyDependencies"
DependsOnTargets="MyGetPackagingOutputs">
<Copy SourceFiles="@(MyPackagingOutputs)"
SkipUnchangedFiles="true"
DestinationFolder="$(OutDir)"
/>
</Target>
</Project>

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
<Identity Name="WindowsTerminal.TestHost" Publisher="CN=Windows Terminal Team" Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="fba054a7-f1a1-4cb7-bb21-4949919af2f5" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>TestHostApp</DisplayName>
<PublisherDisplayName>migrie</PublisherDisplayName>
<Logo>taef.png</Logo>
<uap:SupportedUsers>multiple</uap:SupportedUsers>
</Properties>
<Dependencies>
<!-- We're manually setting the version here, because the CI only runs windows 17763 -->
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.17763.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="taef.executionengine.universal.App" Executable="$targetnametoken$.exe" EntryPoint="TestHostApp.App">
<uap:VisualElements DisplayName="TestHostApp" Square150x150Logo="taef.png" Square44x44Logo="taef.png" Description="TestHostApp" BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="taef.png">
</uap:DefaultTile>
<uap:SplashScreen Image="taef.png" />
</uap:VisualElements>
</Application>
</Applications>
</Package>

View File

@@ -1,162 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}</ProjectGuid>
<RootNamespace>TestHostApp</RootNamespace>
<DefaultLanguage>en-US</DefaultLanguage>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<OutputSubDir>Tests\Data</OutputSubDir>
<UseWmXml>true</UseWmXml>
<ConfigurationType>Application</ConfigurationType>
<NoOutputRedirection>true</NoOutputRedirection>
<!--
These two properties are very important!
Without them, msbuild will stomp MinVersion and MaxVersionTested in the
Package.appxmanifest and replace them with whatever our values for
TargetPlatformMinVersion and TargetPlatformVersion are.
-->
<AppxOSMinVersionReplaceManifestVersion>false</AppxOSMinVersionReplaceManifestVersion>
<AppxOSMaxVersionTestedReplaceManifestVersion>false</AppxOSMaxVersionTestedReplaceManifestVersion>
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)\src\common.build.pre.props" />
<!-- This project is _heavily_ cribbed directly from the TAEF samples. In
order to avoid breaking this project, it's been left largely unmodified. The
only modifications are those near the bottom of the file:
* References to our dependent winrt projects (Connection, Settings, Control,
App)
* Manual copy steps to copy the actual test code (TerminalApp.LocalTests.dll)
and the dlls that TerminalConnection is dependent upon, but don't roll up
here for whatever reason.
-->
<!-- This is important: It somehow convinces the CI to not validate that
"taef.png" is actually in the package. taef.png will get copied to the
OutputPath when taef is run, but if this isn't set to false, then the CI
will try and make sure taef.png is in the OutputPath at build time.-->
<PropertyGroup Label="UserMacros">
<GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<DisableSpecificWarnings>4453;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)
;$(IntermediateOutputPath)
</AdditionalIncludeDirectories>
<PreprocessorDefinitions>INLINE_TEST_METHOD_MARKUP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>$(AdditionalDependencies);windowsapp.lib</AdditionalDependencies>
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="UnitTestApp.xaml.h">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="UnitTestApp.xaml">
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<ClCompile Include="UnitTestApp.xaml.cpp">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<!-- Reference all the WinRT projects that we want to role up into this test project. -->
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj">
<Project>{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj">
<Project>{ca5cad1a-44bd-4ac7-ac72-f16e576fdd12}</Project>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<!-- Some helper paths to find our test code -->
<_TestBinRoot>$(OpenConsoleDir)\bin\$(Platform)\$(Configuration)</_TestBinRoot>
<_CppWinrtBinRoot>$(OpenConsoleDir)\$(Platform)\$(Configuration)</_CppWinrtBinRoot>
<_CppWinrtBinRoot Condition="'$(Platform)'=='Win32'">$(OpenConsoleDir)\$(Configuration)</_CppWinrtBinRoot>
<_TAEFPlatformName>$(Platform)</_TAEFPlatformName>
<_TAEFPlatformName Condition="'$(Platform)'=='Win32'">x86</_TAEFPlatformName>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore">
<HintPath>$(OpenConsoleDir)\packages\Taef.Redist.Wlk.10.48.200103003-develop\lib\Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<!-- This path is _relative to the .winmd_ -->
<Implementation>..\build\Binaries\$(_TAEFPlatformName)\TE.AppxUnitTestClient.dll</Implementation>
</Reference>
</ItemGroup>
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<!-- Use this to auto-find all the dll's that TerminalConnection produces. We
don't roll these up automatically, so we'll need to copy them manually
(below)
The dependencies from TerminalConnection get rolled up in the
GetPackagingOutputs step, when it produces the "appx recipe". This means
they only show up when the build produces an AppX\ folder for either running
or packaging.
It is literally impossible to produce an AppX\ folder using MSBuild without
packaging an appx, and we don't want to do that anyway, so we use these copy
rules instead.
-->
<ItemGroup>
<TerminalConnectionDlls Include="$(_CppWinrtBinRoot)\TerminalConnection\*.dll"/>
</ItemGroup>
<Import Project="$(OpenConsoleDir)\src\common.build.post.props" />
<Target Name="AfterBuild">
<!-- Copy the AppxManifest.xml to another file, because when TAEF is
deploying the app, it'll delete the AppxManifest.xml file from this
directory when it tries to clean up after itself. -->
<Copy SourceFiles="$(TargetDir)\AppxManifest.xml" DestinationFiles="$(TargetDir)\TestHostAppXManifest.xml" />
<!-- Copy our test code from LocalTests_TerminalApp into this directory -->
<Copy SourceFiles="$(_TestBinRoot)\LocalTests_TerminalApp\TerminalApp.LocalTests.dll"
DestinationFiles="$(TargetDir)\TerminalApp.LocalTests.dll" />
<!-- Copy some dlls which TerminalConnection is dependent upon that didn't
get rolled up into this directory -->
<Copy SourceFiles="@(TerminalConnectionDlls)"
DestinationFiles="@(TerminalConnectionDlls->'$(TargetDir)\%(Filename).dll')" />
</Target>
</Project>

View File

@@ -1,8 +0,0 @@
<Application
x:Class="TestHostApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestHostApp"
RequestedTheme="Dark">
</Application>

View File

@@ -1,27 +0,0 @@
//-----------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------
#include "pch.h"
namespace TestHostApp
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
App::App()
{
InitializeComponent();
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs ^ e)
{
Windows::UI::Xaml::Window::Current->Activate();
Microsoft::VisualStudio::TestPlatform::TestExecutor::WinRTCore::UnitTestClient::Run(e->Arguments);
}
}

View File

@@ -1,27 +0,0 @@
//-----------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------
#pragma once
#include "UnitTestApp.g.h"
// NOTE: 03-Jan-2020
// This class is horrifyingly defined in CX, _NOT_ CppWinrt. It was largely
// taken straight from the TAEF sample code. However, it does _work_, and it's
// not about to be changed ever, so it's not worth the effort to try and port it
// to cppwinrt at this time.
namespace TestHostApp
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
ref class App sealed
{
protected:
virtual void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs ^ e) override;
internal :
App();
};
}

View File

@@ -1,6 +0,0 @@
//
// pch.cpp
// Include the standard header and generate the precompiled header.
//
#include "pch.h"

View File

@@ -1,8 +0,0 @@
//
// pch.h
// Header for standard system include files.
//
#pragma once
#include "UnitTestApp.xaml.h"

View File

@@ -48,8 +48,6 @@ Author(s):
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.ui.xaml.input.h>
#include <winrt/Windows.UI.Xaml.Markup.h>
#include <winrt/Windows.UI.Xaml.Documents.h>
#include <windows.ui.xaml.media.dxinterop.h>

View File

@@ -591,30 +591,6 @@ namespace winrt::TerminalApp::implementation
}
}
fire_and_forget AppLogic::_LoadErrorsDialogRoutine()
{
co_await winrt::resume_foreground(_root->Dispatcher());
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
}
fire_and_forget AppLogic::_ShowLoadWarningsDialogRoutine()
{
co_await winrt::resume_foreground(_root->Dispatcher());
_ShowLoadWarningsDialog();
}
fire_and_forget AppLogic::_RefreshThemeRoutine()
{
co_await winrt::resume_foreground(_root->Dispatcher());
// Refresh the UI theme
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
}
// Method Description:
// - Reloads the settings from the profile.json.
void AppLogic::_ReloadSettings()
@@ -628,12 +604,19 @@ namespace winrt::TerminalApp::implementation
if (FAILED(_settingsLoadedResult))
{
_LoadErrorsDialogRoutine();
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
});
return;
}
else if (_settingsLoadedResult == S_FALSE)
{
_ShowLoadWarningsDialogRoutine();
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
_ShowLoadWarningsDialog();
});
}
// Here, we successfully reloaded the settings, and created a new
@@ -642,7 +625,10 @@ namespace winrt::TerminalApp::implementation
// Update the settings in TerminalPage
_root->SetSettings(_settings, true);
_RefreshThemeRoutine();
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
// Refresh the UI theme
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
});
}
// Method Description:

View File

@@ -77,10 +77,6 @@ namespace winrt::TerminalApp::implementation
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
void _ShowLoadWarningsDialog();
fire_and_forget _LoadErrorsDialogRoutine();
fire_and_forget _ShowLoadWarningsDialogRoutine();
fire_and_forget _RefreshThemeRoutine();
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
[[nodiscard]] HRESULT _TryLoadSettings() noexcept;

View File

@@ -29,7 +29,6 @@ namespace TerminalAppLocalTests
class ProfileTests;
class ColorSchemeTests;
class KeyBindingsTests;
class TabTests;
};
namespace TerminalAppUnitTests
{
@@ -121,6 +120,5 @@ private:
friend class TerminalAppLocalTests::ProfileTests;
friend class TerminalAppLocalTests::ColorSchemeTests;
friend class TerminalAppLocalTests::KeyBindingsTests;
friend class TerminalAppLocalTests::TabTests;
friend class TerminalAppUnitTests::DynamicProfileTests;
};

View File

@@ -358,6 +358,25 @@ void Pane::Close()
_ClosedHandlers(nullptr, nullptr);
}
// Method Description:
// - Prepare this pane to be removed from the UI hierarchy by closing all controls
// and connections beneath it.
void Pane::Shutdown()
{
// Lock the create/close lock so that another operation won't concurrently
// modify our tree
std::unique_lock lock{ _createCloseLock };
if (_IsLeaf())
{
_control.Close();
}
else
{
_firstChild->Shutdown();
_secondChild->Shutdown();
}
}
// Method Description:
// - Get the root UIElement of this pane. There may be a single TermControl as a
// child, or an entire tree of grids and panes as children of this element.
@@ -734,18 +753,6 @@ void Pane::_CloseChild(const bool closeFirst)
}
}
winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
{
auto weakThis{ shared_from_this() };
co_await winrt::resume_foreground(_root.Dispatcher());
if (auto pane{ weakThis.get() })
{
_CloseChild(closeFirst);
}
}
// Method Description:
// - Adds event handlers to our children to handle their close events.
// Arguments:
@@ -755,11 +762,15 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
void Pane::_SetupChildCloseHandlers()
{
_firstClosedToken = _firstChild->Closed([this](auto&& /*s*/, auto&& /*e*/) {
_CloseChildRoutine(true);
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
_CloseChild(true);
});
});
_secondClosedToken = _secondChild->Closed([this](auto&& /*s*/, auto&& /*e*/) {
_CloseChildRoutine(false);
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
_CloseChild(false);
});
});
}

View File

@@ -64,6 +64,7 @@ public:
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
void Shutdown();
void Close();
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
@@ -115,7 +116,6 @@ private:
bool _NavigateFocus(const winrt::TerminalApp::Direction& direction);
void _CloseChild(const bool closeFirst);
winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst);
void _FocusFirstChild();
void _ControlConnectionStateChangedHandler(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);

View File

@@ -146,7 +146,7 @@ void Tab::_Focus()
}
}
winrt::fire_and_forget Tab::UpdateIcon(const winrt::hstring iconPath)
void Tab::UpdateIcon(const winrt::hstring iconPath)
{
// Don't reload our icon if it hasn't changed.
if (iconPath == _lastIconPath)
@@ -158,12 +158,12 @@ winrt::fire_and_forget Tab::UpdateIcon(const winrt::hstring iconPath)
std::weak_ptr<Tab> weakThis{ shared_from_this() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
if (auto tab{ weakThis.lock() })
{
_tabViewItem.IconSource(GetColoredIcon<winrt::MUX::Controls::IconSource>(_lastIconPath));
}
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
if (auto tab{ weakThis.lock() })
{
tab->_tabViewItem.IconSource(GetColoredIcon<winrt::MUX::Controls::IconSource>(tab->_lastIconPath));
}
});
}
// Method Description:
@@ -185,18 +185,18 @@ winrt::hstring Tab::GetActiveTitle() const
// - text: The new text string to use as the Header for our TabViewItem
// Return Value:
// - <none>
winrt::fire_and_forget Tab::SetTabText(const winrt::hstring text)
void Tab::SetTabText(const winrt::hstring& text)
{
// Copy the hstring, so we don't capture a dead reference
winrt::hstring textCopy{ text };
std::weak_ptr<Tab> weakThis{ shared_from_this() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
if (auto tab{ weakThis.lock() })
{
_tabViewItem.Header(winrt::box_value(text));
}
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [text = std::move(textCopy), weakThis]() {
if (auto tab{ weakThis.lock() })
{
tab->_tabViewItem.Header(winrt::box_value(text));
}
});
}
// Method Description:
@@ -207,14 +207,13 @@ winrt::fire_and_forget Tab::SetTabText(const winrt::hstring text)
// - delta: a number of lines to move the viewport relative to the current viewport.
// Return Value:
// - <none>
winrt::fire_and_forget Tab::Scroll(const int delta)
void Tab::Scroll(const int delta)
{
auto control = GetActiveTerminalControl();
co_await winrt::resume_foreground(control.Dispatcher());
const auto currentOffset = control.GetScrollOffset();
control.KeyboardScrollViewport(currentOffset + delta);
control.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta]() {
const auto currentOffset = control.GetScrollOffset();
control.KeyboardScrollViewport(currentOffset + delta);
});
}
// Method Description:
@@ -298,6 +297,13 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
_rootPane->NavigateFocus(direction);
}
// Method Description:
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
void Tab::Shutdown()
{
_rootPane->Shutdown();
}
// Method Description:
// - Closes the currently focused pane in this tab. If it's the last pane in
// this tab, our Closed event will be fired (at a later time) for anyone
@@ -366,7 +372,6 @@ void Tab::_AttachEventHandlersToPane(std::shared_ptr<Pane> pane)
pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.lock() };
if (tab && sender != tab->_activePane)
{
// Clear the active state of the entire tree, and mark only the sender as active.

View File

@@ -20,23 +20,24 @@ public:
bool IsFocused() const noexcept;
void SetFocused(const bool focused);
winrt::fire_and_forget Scroll(const int delta);
void Scroll(const int delta);
bool CanSplitPane(winrt::TerminalApp::SplitState splitType);
void SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
void UpdateIcon(const winrt::hstring iconPath);
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::TerminalApp::Direction& direction);
void NavigateFocus(const winrt::TerminalApp::Direction& direction);
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
winrt::hstring GetActiveTitle() const;
winrt::fire_and_forget SetTabText(const winrt::hstring text);
void SetTabText(const winrt::hstring& text);
void Shutdown();
void ClosePane();
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);

View File

@@ -63,7 +63,10 @@ namespace winrt::TerminalApp::implementation
_tabView = _tabRow.TabView();
_rearranging = false;
_tabView.TabDragStarting([weakThis{ get_weak() }](auto&& /*o*/, auto&& /*a*/) {
// weak_ptr to this TerminalPage object lambda capturing
auto weakThis{ get_weak() };
_tabView.TabDragStarting([weakThis](auto&& /*o*/, auto&& /*a*/) {
if (auto page{ weakThis.get() })
{
page->_rearranging = true;
@@ -72,7 +75,7 @@ namespace winrt::TerminalApp::implementation
}
});
_tabView.TabDragCompleted([weakThis{ get_weak() }](auto&& /*o*/, auto&& /*a*/) {
_tabView.TabDragCompleted([weakThis](auto&& /*o*/, auto&& /*a*/) {
if (auto page{ weakThis.get() })
{
auto& from{ page->_rearrangeFrom };
@@ -113,7 +116,7 @@ namespace winrt::TerminalApp::implementation
_RegisterActionCallbacks();
//Event Bindings (Early)
_newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) {
_newTabButton.Click([weakThis](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_OpenNewTab(nullptr);
@@ -320,7 +323,9 @@ namespace winrt::TerminalApp::implementation
profileMenuItem.FontWeight(FontWeights::Bold());
}
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
auto weakThis{ get_weak() };
profileMenuItem.Click([profileIndex, weakThis](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
auto newTerminalArgs = winrt::make_self<winrt::TerminalApp::implementation::NewTerminalArgs>();
@@ -427,13 +432,6 @@ namespace winrt::TerminalApp::implementation
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
}
winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page)
{
co_await winrt::resume_foreground(page->_tabView.Dispatcher());
page->_RemoveTabViewItem(tabViewItem);
}
// Method Description:
// - Creates a new tab with the given settings. If the tab bar is not being
// currently displayed, it will be shown.
@@ -457,10 +455,11 @@ namespace winrt::TerminalApp::implementation
// Don't capture a strong ref to the tab. If the tab is removed as this
// is called, we don't really care anymore about handling the event.
std::weak_ptr<Tab> weakTabPtr = newTab;
auto weakThis{ get_weak() };
// When the tab's active pane changes, we'll want to lookup a new icon
// for it, and possibly propogate the title up to the window.
newTab->ActivePaneChanged([weakTabPtr, weakThis{ get_weak() }]() {
newTab->ActivePaneChanged([weakTabPtr, weakThis]() {
auto page{ weakThis.get() };
auto tab{ weakTabPtr.lock() };
@@ -487,10 +486,15 @@ namespace winrt::TerminalApp::implementation
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick });
// When the tab is closed, remove it from our list of tabs.
newTab->Closed([tabViewItem, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
newTab->Closed([tabViewItem, weakThis](auto&& /*s*/, auto&& /*e*/) {
if (auto page{ weakThis.get() })
{
page->_RemoveOnCloseRoutine(tabViewItem, page);
page->_tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tabViewItem, weakThis]() {
if (auto page{ weakThis.get() })
{
page->_RemoveTabViewItem(tabViewItem);
}
});
}
});
@@ -753,16 +757,20 @@ namespace winrt::TerminalApp::implementation
// - tabIndex: the index of the tab to be removed
void TerminalPage::_RemoveTabViewItemByIndex(uint32_t tabIndex)
{
// Removing the tab from the collection should destroy its control and disconnect its connection,
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
auto iterator = _tabs.begin() + tabIndex;
(*iterator)->Shutdown();
_tabs.erase(iterator);
_tabView.TabItems().RemoveAt(tabIndex);
// To close the window here, we need to close the hosting window.
if (_tabs.size() == 1)
if (_tabs.size() == 0)
{
_lastTabClosedHandlers(*this, nullptr);
}
// Removing the tab from the collection will destroy its control and disconnect its connection.
_tabs.erase(_tabs.begin() + tabIndex);
_tabView.TabItems().RemoveAt(tabIndex);
auto focusedTabIndex = _GetFocusedTabIndex();
if (gsl::narrow_cast<int>(tabIndex) == focusedTabIndex)
{
@@ -804,8 +812,9 @@ namespace winrt::TerminalApp::implementation
// Don't capture a strong ref to the tab. If the tab is removed as this
// is called, we don't really care anymore about handling the event.
std::weak_ptr<Tab> weakTabPtr = hostingTab;
auto weakThis{ get_weak() };
term.TitleChanged([weakTabPtr, weakThis{ get_weak() }](auto newTitle) {
term.TitleChanged([weakTabPtr, weakThis](auto newTitle) {
auto page{ weakThis.get() };
auto tab{ weakTabPtr.lock() };
@@ -885,19 +894,18 @@ namespace winrt::TerminalApp::implementation
return -1;
}
winrt::fire_and_forget TerminalPage::_SetFocusedTabIndex(int tabIndex)
void TerminalPage::_SetFocusedTabIndex(int tabIndex)
{
// GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex)
// sometimes set focus to an incorrect tab after removing some tabs
auto tab = _tabs.at(tabIndex);
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_tabView.Dispatcher());
if (auto page{ weakThis.get() })
{
auto tab = _tabs.at(tabIndex);
_tabView.SelectedItem(tab->GetTabViewItem());
}
_tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tab, weakThis]() {
if (auto page{ weakThis.get() })
{
page->_tabView.SelectedItem(tab->GetTabViewItem());
}
});
}
// Method Description:
@@ -1149,37 +1157,37 @@ namespace winrt::TerminalApp::implementation
// terminal control raises it's CopyToClipboard event.
// Arguments:
// - copiedData: the new string content to place on the clipboard.
winrt::fire_and_forget TerminalPage::_CopyToClipboardHandler(const IInspectable /*sender*/,
const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs copiedData)
void TerminalPage::_CopyToClipboardHandler(const IInspectable& /*sender*/,
const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs& copiedData)
{
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::High);
this->Dispatcher().RunAsync(CoreDispatcherPriority::High, [copiedData]() {
DataPackage dataPack = DataPackage();
dataPack.RequestedOperation(DataPackageOperation::Copy);
DataPackage dataPack = DataPackage();
dataPack.RequestedOperation(DataPackageOperation::Copy);
// copy text to dataPack
dataPack.SetText(copiedData.Text());
// copy text to dataPack
dataPack.SetText(copiedData.Text());
// copy html to dataPack
const auto htmlData = copiedData.Html();
if (!htmlData.empty())
{
dataPack.SetHtmlFormat(htmlData);
}
// copy html to dataPack
const auto htmlData = copiedData.Html();
if (!htmlData.empty())
{
dataPack.SetHtmlFormat(htmlData);
}
// copy rtf data to dataPack
const auto rtfData = copiedData.Rtf();
if (!rtfData.empty())
{
dataPack.SetRtf(rtfData);
}
// copy rtf data to dataPack
const auto rtfData = copiedData.Rtf();
if (!rtfData.empty())
{
dataPack.SetRtf(rtfData);
}
try
{
Clipboard::SetContent(dataPack);
Clipboard::Flush();
}
CATCH_LOG();
try
{
Clipboard::SetContent(dataPack);
Clipboard::Flush();
}
CATCH_LOG();
});
}
// Method Description:
@@ -1188,12 +1196,12 @@ namespace winrt::TerminalApp::implementation
// data with it's PasteFromClipboard event.
// Arguments:
// - eventArgs: the PasteFromClipboard event sent from the TermControl
winrt::fire_and_forget TerminalPage::_PasteFromClipboardHandler(const IInspectable /*sender*/,
const PasteFromClipboardEventArgs eventArgs)
void TerminalPage::_PasteFromClipboardHandler(const IInspectable& /*sender*/,
const PasteFromClipboardEventArgs& eventArgs)
{
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::High);
TerminalPage::PasteFromClipboard(eventArgs);
this->Dispatcher().RunAsync(CoreDispatcherPriority::High, [eventArgs]() {
TerminalPage::PasteFromClipboard(eventArgs);
});
}
// Function Description:
@@ -1387,7 +1395,7 @@ namespace winrt::TerminalApp::implementation
// This includes update the settings of all the tabs according
// to their profiles, update the title and icon of each tab, and
// finally create the tab flyout
winrt::fire_and_forget TerminalPage::_RefreshUIForSettingsReload()
void TerminalPage::_RefreshUIForSettingsReload()
{
// Re-wire the keybindings to their handlers, as we'll have created a
// new AppKeyBindings object.
@@ -1415,16 +1423,15 @@ namespace winrt::TerminalApp::implementation
}
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
// repopulate the new tab button's flyout with entries for each
// profile, which might have changed
if (auto page{ weakThis.get() })
{
_UpdateTabWidthMode();
_CreateNewTabFlyout();
}
this->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
// repopulate the new tab button's flyout with entries for each
// profile, which might have changed
if (auto page{ weakThis.get() })
{
page->_UpdateTabWidthMode();
page->_CreateNewTabFlyout();
}
});
}
// Method Description:

View File

@@ -12,12 +12,6 @@
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct TerminalPage : TerminalPageT<TerminalPage>
@@ -103,13 +97,11 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl();
int _GetFocusedTabIndex() const;
winrt::fire_and_forget _SetFocusedTabIndex(int tabIndex);
void _SetFocusedTabIndex(int tabIndex);
void _CloseFocusedTab();
void _CloseFocusedPane();
void _CloseAllTabs();
winrt::fire_and_forget _RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page);
// Todo: add more event implementations here
// MSFT:20641986: Add keybindings for New Window
void _Scroll(int delta);
@@ -118,9 +110,9 @@ namespace winrt::TerminalApp::implementation
void _ScrollPage(int delta);
void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord);
winrt::fire_and_forget _CopyToClipboardHandler(const IInspectable sender, const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs copiedData);
winrt::fire_and_forget _PasteFromClipboardHandler(const IInspectable sender,
const Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs eventArgs);
void _CopyToClipboardHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs& copiedData);
void _PasteFromClipboardHandler(const IInspectable& sender,
const Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs& eventArgs);
bool _CopyText(const bool trimTrailingWhitespace);
void _PasteText();
static fire_and_forget PasteFromClipboard(winrt::Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs eventArgs);
@@ -135,7 +127,7 @@ namespace winrt::TerminalApp::implementation
void _Find();
winrt::fire_and_forget _RefreshUIForSettingsReload();
void _RefreshUIForSettingsReload();
void _ToggleFullscreen();
@@ -165,8 +157,6 @@ namespace winrt::TerminalApp::implementation
void _HandleResetFontSize(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleToggleFullscreen(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
#pragma endregion
friend class TerminalAppLocalTests::TabTests;
};
}

View File

@@ -59,23 +59,32 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// set the input scope to Text because this control is for any text.
_editContext.InputScope(Core::CoreTextInputScope::Text);
_editContext.TextRequested({ this, &TSFInputControl::_textRequestedHandler });
_textRequestedRevoker = _editContext.TextRequested(winrt::auto_revoke, { this, &TSFInputControl::_textRequestedHandler });
_editContext.SelectionRequested({ this, &TSFInputControl::_selectionRequestedHandler });
_selectionRequestedRevoker = _editContext.SelectionRequested(winrt::auto_revoke, { this, &TSFInputControl::_selectionRequestedHandler });
_editContext.FocusRemoved({ this, &TSFInputControl::_focusRemovedHandler });
_focusRemovedRevoker = _editContext.FocusRemoved(winrt::auto_revoke, { this, &TSFInputControl::_focusRemovedHandler });
_editContext.TextUpdating({ this, &TSFInputControl::_textUpdatingHandler });
_textUpdatingRevoker = _editContext.TextUpdating(winrt::auto_revoke, { this, &TSFInputControl::_textUpdatingHandler });
_editContext.SelectionUpdating({ this, &TSFInputControl::_selectionUpdatingHandler });
_selectionUpdatingRevoker = _editContext.SelectionUpdating(winrt::auto_revoke, { this, &TSFInputControl::_selectionUpdatingHandler });
_editContext.FormatUpdating({ this, &TSFInputControl::_formatUpdatingHandler });
_formatUpdatingRevoker = _editContext.FormatUpdating(winrt::auto_revoke, { this, &TSFInputControl::_formatUpdatingHandler });
_editContext.LayoutRequested({ this, &TSFInputControl::_layoutRequestedHandler });
_layoutRequestedRevoker = _editContext.LayoutRequested(winrt::auto_revoke, { this, &TSFInputControl::_layoutRequestedHandler });
_editContext.CompositionStarted({ this, &TSFInputControl::_compositionStartedHandler });
_compositionStartedRevoker = _editContext.CompositionStarted(winrt::auto_revoke, { this, &TSFInputControl::_compositionStartedHandler });
_editContext.CompositionCompleted({ this, &TSFInputControl::_compositionCompletedHandler });
_compositionCompletedRevoker = _editContext.CompositionCompleted(winrt::auto_revoke, { this, &TSFInputControl::_compositionCompletedHandler });
}
// Method Description:
// - Prepares this TSFInputControl to be removed from the UI hierarchy.
void TSFInputControl::Close()
{
// Explicitly disconnect the LayoutRequested handler -- it can cause problems during application teardown.
// See GH#4159 for more info.
_layoutRequestedRevoker.revoke();
}
// Method Description:

View File

@@ -37,6 +37,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void NotifyFocusEnter();
void NotifyFocusLeave();
void Close();
static void OnCompositionChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
// -------------------------------- WinRT Events ---------------------------------
@@ -55,6 +57,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _textUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextTextUpdatingEventArgs const& args);
void _formatUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextFormatUpdatingEventArgs const& args);
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextRequested_revoker _textRequestedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionRequested_revoker _selectionRequestedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::FocusRemoved_revoker _focusRemovedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextUpdating_revoker _textUpdatingRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionUpdating_revoker _selectionUpdatingRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::FormatUpdating_revoker _formatUpdatingRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::LayoutRequested_revoker _layoutRequestedRevoker;
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;

View File

@@ -27,5 +27,7 @@ namespace Microsoft.Terminal.TerminalControl
void NotifyFocusEnter();
void NotifyFocusLeave();
void Close();
}
}

View File

@@ -255,46 +255,45 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - newSettings: New settings values for the profile in this terminal.
// Return Value:
// - <none>
winrt::fire_and_forget TermControl::UpdateSettings(Settings::IControlSettings newSettings)
void TermControl::UpdateSettings(Settings::IControlSettings newSettings)
{
_settings = newSettings;
auto weakThis{ get_weak() };
// Dispatch a call to the UI thread to apply the new settings to the
// terminal.
co_await winrt::resume_foreground(_root.Dispatcher());
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
// Update our control settings
_ApplyUISettings();
// Update DxEngine's SelectionBackground
_renderEngine->SetSelectionBackground(_settings.SelectionBackground());
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
// Refresh our font with the renderer
_UpdateFont();
const auto width = _swapChainPanel.ActualWidth();
const auto height = _swapChainPanel.ActualHeight();
if (width != 0 && height != 0)
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis{ get_weak() }, this]() {
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
// If the font size changed, or the _swapchainPanel's size changed
// for any reason, we'll need to make sure to also resize the
// buffer. _DoResize will invalidate everything for us.
auto lock = _terminal->LockForWriting();
_DoResize(width, height);
}
// Update our control settings
_ApplyUISettings();
// set TSF Foreground
Media::SolidColorBrush foregroundBrush{};
foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground()));
_tsfInputControl.Foreground(foregroundBrush);
}
// Update DxEngine's SelectionBackground
_renderEngine->SetSelectionBackground(_settings.SelectionBackground());
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
// Refresh our font with the renderer
_UpdateFont();
const auto width = _swapChainPanel.ActualWidth();
const auto height = _swapChainPanel.ActualHeight();
if (width != 0 && height != 0)
{
// If the font size changed, or the _swapchainPanel's size changed
// for any reason, we'll need to make sure to also resize the
// buffer. _DoResize will invalidate everything for us.
auto lock = _terminal->LockForWriting();
_DoResize(width, height);
}
// set TSF Foreground
Media::SolidColorBrush foregroundBrush{};
foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground()));
_tsfInputControl.Foreground(foregroundBrush);
}
});
}
// Method Description:
@@ -452,38 +451,37 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - color: The background color to use as a uint32 (aka DWORD COLORREF)
// Return Value:
// - <none>
winrt::fire_and_forget TermControl::_BackgroundColorChanged(const uint32_t color)
void TermControl::_BackgroundColorChanged(const uint32_t color)
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_root.Dispatcher());
if (auto control{ weakThis.get() })
{
const auto R = GetRValue(color);
const auto G = GetGValue(color);
const auto B = GetBValue(color);
winrt::Windows::UI::Color bgColor{};
bgColor.R = R;
bgColor.G = G;
bgColor.B = B;
bgColor.A = 255;
if (auto acrylic = _root.Background().try_as<Media::AcrylicBrush>())
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis{ get_weak() }, this, color]() {
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
acrylic.FallbackColor(bgColor);
acrylic.TintColor(bgColor);
}
else if (auto solidColor = _root.Background().try_as<Media::SolidColorBrush>())
{
solidColor.Color(bgColor);
}
const auto R = GetRValue(color);
const auto G = GetGValue(color);
const auto B = GetBValue(color);
// Set the default background as transparent to prevent the
// DX layer from overwriting the background image or acrylic effect
_settings.DefaultBackground(ARGB(0, R, G, B));
}
winrt::Windows::UI::Color bgColor{};
bgColor.R = R;
bgColor.G = G;
bgColor.B = B;
bgColor.A = 255;
if (auto acrylic = _root.Background().try_as<Media::AcrylicBrush>())
{
acrylic.FallbackColor(bgColor);
acrylic.TintColor(bgColor);
}
else if (auto solidColor = _root.Background().try_as<Media::SolidColorBrush>())
{
solidColor.Color(bgColor);
}
// Set the default background as transparent to prevent the
// DX layer from overwriting the background image or acrylic effect
_settings.DefaultBackground(ARGB(0, R, G, B));
}
});
}
TermControl::~TermControl()
@@ -538,7 +536,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return _connection.State();
}
winrt::fire_and_forget TermControl::SwapChainChanged()
void TermControl::SwapChainChanged()
{
if (!_initializedTerminal)
{
@@ -546,33 +544,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
auto chain = _renderEngine->GetSwapChain();
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_swapChainPanel.Dispatcher());
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
auto lock = _terminal->LockForWriting();
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
}
}
winrt::fire_and_forget TermControl::_SwapChainRoutine()
{
auto chain = _renderEngine->GetSwapChain();
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_swapChainPanel.Dispatcher());
if (auto control{ weakThis.get() })
{
_terminal->LockConsole();
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
_terminal->UnlockConsole();
}
_swapChainPanel.Dispatcher().RunAsync(CoreDispatcherPriority::High, [weakThis{ get_weak() }, this, chain]() {
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
auto lock = _terminal->LockForWriting();
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
}
});
}
bool TermControl::_InitializeTerminal()
@@ -656,7 +637,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
_terminal->SetWriteInputCallback(inputFn);
_SwapChainRoutine();
auto chain = _renderEngine->GetSwapChain();
_swapChainPanel.Dispatcher().RunAsync(CoreDispatcherPriority::High, [weakThis{ get_weak() }, this, chain]() {
if (auto control{ weakThis.get() })
{
_terminal->LockConsole();
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
_terminal->UnlockConsole();
}
});
// Set up the height of the ScrollViewer and the grid we're using to fake our scrolling height
auto bottom = _terminal->GetViewport().BottomExclusive();
@@ -1622,9 +1613,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// of the buffer.
// - viewHeight: the height of the viewport in rows.
// - bufferSize: the length of the buffer, in rows
winrt::fire_and_forget TermControl::_TerminalScrollPositionChanged(const int viewTop,
const int viewHeight,
const int bufferSize)
void TermControl::_TerminalScrollPositionChanged(const int viewTop,
const int viewHeight,
const int bufferSize)
{
// Since this callback fires from non-UI thread, we might be already
// closed/closing.
@@ -1633,25 +1624,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return;
}
// Update our scrollbar
_scrollBar.Dispatcher().RunAsync(CoreDispatcherPriority::Low, [weakThis{ get_weak() }, this, viewTop, viewHeight, bufferSize]() {
// Even if we weren't closed/closing few lines above, we might be
// while waiting for this block of code to be dispatched.
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
if (_closing.load())
{
return;
}
_ScrollbarUpdater(_scrollBar, viewTop, viewHeight, bufferSize);
}
});
// Set this value as our next expected scroll position.
_lastScrollOffset = { viewTop };
_scrollPositionChangedHandlers(viewTop, viewHeight, bufferSize);
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_scrollBar.Dispatcher());
// Even if we weren't closed/closing few lines above, we might be
// while waiting for this block of code to be dispatched.
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
if (!_closing.load())
{
// Update our scrollbar
_ScrollbarUpdater(_scrollBar, viewTop, viewHeight, bufferSize);
}
}
}
hstring TermControl::Title()
@@ -1734,6 +1725,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connection.TerminalOutput(_connectionOutputEventToken);
_connectionStateChangedRevoker.revoke();
_tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
if (auto localConnection{ std::exchange(_connection, nullptr) })
{
localConnection.Close();

View File

@@ -58,7 +58,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
TermControl();
TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection);
winrt::fire_and_forget UpdateSettings(Settings::IControlSettings newSettings);
void UpdateSettings(Settings::IControlSettings newSettings);
hstring Title();
@@ -77,7 +77,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void AdjustFontSize(int fontSizeDelta);
void ResetFontSize();
winrt::fire_and_forget SwapChainChanged();
void SwapChainChanged();
void CreateSearchBoxControl();
@@ -173,7 +173,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _Create();
void _ApplyUISettings();
void _InitializeBackgroundBrush();
winrt::fire_and_forget _BackgroundColorChanged(const uint32_t color);
void _BackgroundColorChanged(const uint32_t color);
bool _InitializeTerminal();
void _UpdateFont(const bool initialUpdate = false);
void _SetFontSize(int fontSize);
@@ -191,12 +191,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
void _SendInputToConnection(const std::wstring& wstr);
void _SendPastedTextToConnection(const std::wstring& wstr);
winrt::fire_and_forget _SwapChainRoutine();
void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args);
void _DoResize(const double newWidth, const double newHeight);
void _TerminalTitleChanged(const std::wstring_view& wstr);
winrt::fire_and_forget _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
void _MouseScrollHandler(const double delta, Windows::UI::Input::PointerPoint const& pointerPoint);
void _MouseZoomHandler(const double delta);

View File

@@ -28,11 +28,6 @@ namespace Microsoft::Terminal::Core
class Terminal;
}
// fwdecl unittest classes
#ifdef UNIT_TESTING
class ConptyRoundtripTests;
#endif
class Microsoft::Terminal::Core::Terminal final :
public Microsoft::Terminal::Core::ITerminalApi,
public Microsoft::Terminal::Core::ITerminalInput,
@@ -250,8 +245,4 @@ private:
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 ::ConptyRoundtripTests;
#endif
};

View File

@@ -74,27 +74,6 @@ try
}
CATCH_LOG_RETURN_FALSE()
bool TerminalDispatch::LineFeed(const DispatchTypes::LineFeedType lineFeedType) noexcept
try
{
switch (lineFeedType)
{
case DispatchTypes::LineFeedType::DependsOnMode:
// There is currently no need for mode-specific line feeds in the Terminal,
// so for now we just treat them as a line feed without carriage return.
case DispatchTypes::LineFeedType::WithoutReturn:
Execute(L'\n');
return true;
case DispatchTypes::LineFeedType::WithReturn:
Execute(L'\r');
Execute(L'\n');
return true;
default:
return false;
}
}
CATCH_LOG_RETURN_FALSE()
bool TerminalDispatch::EraseCharacters(const size_t numChars) noexcept
try
{
@@ -102,14 +81,6 @@ try
}
CATCH_LOG_RETURN_FALSE()
bool TerminalDispatch::CarriageReturn() noexcept
try
{
const auto cursorPos = _terminalApi.GetCursorPosition();
return _terminalApi.SetCursorPosition(0, cursorPos.Y);
}
CATCH_LOG_RETURN_FALSE()
bool TerminalDispatch::SetWindowTitle(std::wstring_view title) noexcept
try
{

View File

@@ -22,10 +22,7 @@ public:
bool CursorBackward(const size_t distance) noexcept override;
bool CursorUp(const size_t distance) noexcept override;
bool LineFeed(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::LineFeedType lineFeedType) noexcept override;
bool EraseCharacters(const size_t numChars) noexcept override;
bool CarriageReturn() noexcept override;
bool SetWindowTitle(std::wstring_view title) noexcept override;
bool SetColorTableEntry(const size_t tableIndex, const DWORD color) noexcept override;

View File

@@ -1,366 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// This test class creates an in-proc conpty host as well as a Terminal, to
// validate that strings written to the conpty create the same resopnse on the
// terminal end. Tests can be written that validate both the contents of the
// host buffer as well as the terminal buffer. Everytime that
// `renderer.PaintFrame()` is called, the tests will validate the expected
// output, and then flush the output of the VtEngine straight to the Terminal.
#include "precomp.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../../types/inc/convert.hpp"
#include "../renderer/inc/DummyRenderTarget.hpp"
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/vt/Xterm256Engine.hpp"
#include "../../renderer/vt/XtermEngine.hpp"
#include "../../renderer/vt/WinTelnetEngine.hpp"
class InputBuffer; // This for some reason needs to be fwd-decl'd
#include "../host/inputBuffer.hpp"
#include "../host/readDataCooked.hpp"
#include "test/CommonState.hpp"
#include "../cascadia/TerminalCore/Terminal.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::VirtualTerminal;
using namespace Microsoft::Console;
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Terminal::Core;
class ConptyRoundtripTests
{
TEST_CLASS(ConptyRoundtripTests);
TEST_CLASS_SETUP(ClassSetup)
{
m_state = std::make_unique<CommonState>();
m_state->InitEvents();
m_state->PrepareGlobalFont();
m_state->PrepareGlobalScreenBuffer();
m_state->PrepareGlobalInputBuffer();
return true;
}
TEST_CLASS_CLEANUP(ClassCleanup)
{
m_state->CleanupGlobalScreenBuffer();
m_state->CleanupGlobalFont();
m_state->CleanupGlobalInputBuffer();
m_state.release();
return true;
}
TEST_METHOD_SETUP(MethodSetup)
{
// STEP 1: Set up the Terminal
term = std::make_unique<Terminal>();
term->Create({ CommonState::s_csBufferWidth, CommonState::s_csBufferHeight }, 0, emptyRT);
// STEP 2: Set up the Conpty
// Set up some sane defaults
auto& g = ServiceLocator::LocateGlobals();
auto& gci = g.getConsoleInformation();
gci.SetDefaultForegroundColor(INVALID_COLOR);
gci.SetDefaultBackgroundColor(INVALID_COLOR);
gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
m_state->PrepareNewTextBufferInfo(true);
auto& currentBuffer = gci.GetActiveOutputBuffer();
// Make sure a test hasn't left us in the alt buffer on accident
VERIFY_IS_FALSE(currentBuffer._IsAltBuffer());
VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
VERIFY_ARE_EQUAL(COORD({ 0, 0 }), currentBuffer.GetTextBuffer().GetCursor().GetPosition());
g.pRender = new Renderer(&gci.renderData, nullptr, 0, nullptr);
// Set up an xterm-256 renderer for conpty
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
Viewport initialViewport = currentBuffer.GetViewport();
_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hFile),
gci,
initialViewport,
gci.GetColorTable(),
static_cast<WORD>(gci.GetColorTableSize()));
auto pfn = std::bind(&ConptyRoundtripTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
_pVtRenderEngine->SetTestCallback(pfn);
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
gci.GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
expectedOutput.clear();
return true;
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
m_state->CleanupNewTextBufferInfo();
auto& g = ServiceLocator::LocateGlobals();
delete g.pRender;
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
term = nullptr;
return true;
}
TEST_METHOD(ConptyOutputTestCanary);
TEST_METHOD(SimpleWriteOutputTest);
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);
private:
bool _writeCallback(const char* const pch, size_t const cch);
void _flushFirstFrame();
std::deque<std::string> expectedOutput;
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
std::unique_ptr<CommonState> m_state;
DummyRenderTarget emptyRT;
std::unique_ptr<Terminal> term;
};
bool ConptyRoundtripTests::_writeCallback(const char* const pch, size_t const cch)
{
std::string actualString = std::string(pch, cch);
VERIFY_IS_GREATER_THAN(expectedOutput.size(),
static_cast<size_t>(0),
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()));
std::string first = expectedOutput.front();
expectedOutput.pop_front();
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
VERIFY_ARE_EQUAL(first.length(), cch);
VERIFY_ARE_EQUAL(first, actualString);
// Write the string back to our Terminal
const auto converted = ConvertToW(CP_UTF8, actualString);
term->Write(converted);
return true;
}
void ConptyRoundtripTests::_flushFirstFrame()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
expectedOutput.push_back("\x1b[2J");
expectedOutput.push_back("\x1b[m");
expectedOutput.push_back("\x1b[H"); // Go Home
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
// Function Description:
// - Helper function to validate that a number of characters in a row are all
// the same. Validates that the next end-start characters are all equal to the
// provided string. Will move the provided iterator as it validates. The
// caller should ensure that `iter` starts where they would like to validate.
// Arguments:
// - expectedChar: The character (or characters) we're expecting
// - iter: a iterator pointing to the cell we'd like to start validating at.
// - start: the first index in the range we'd like to validate
// - end: the last index in the range we'd like to validate
// Return Value:
// - <none>
void _verifySpanOfText(const wchar_t* const expectedChar,
TextBufferCellIterator& iter,
const int start,
const int end)
{
for (int x = start; x < end; x++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
if (iter->Chars() != expectedChar)
{
Log::Comment(NoThrowString().Format(L"character [%d] was mismatched", x));
}
VERIFY_ARE_EQUAL(expectedChar, (iter++)->Chars());
}
Log::Comment(NoThrowString().Format(
L"Successfully validated %d characters were '%s'", end - start, expectedChar));
}
// Function Description:
// - Helper function to validate that the next characters pointed to by `iter`
// are the provided string. Will increment iter as it walks the provided
// string of characters. It will leave `iter` on the first character after the
// expectedString.
// Arguments:
// - expectedString: The characters we're expecting
// - iter: a iterator pointing to the cell we'd like to start validating at.
// Return Value:
// - <none>
void _verifyExpectedString(std::wstring_view expectedString,
TextBufferCellIterator& iter)
{
for (const auto wch : expectedString)
{
wchar_t buffer[]{ wch, L'\0' };
std::wstring_view view{ buffer, 1 };
VERIFY_IS_TRUE(iter, L"Ensure iterator is still valid");
VERIFY_ARE_EQUAL(view, (iter++)->Chars(), NoThrowString().Format(L"%s", view.data()));
}
}
// Function Description:
// - Helper function to validate that the next characters in the buffer at the
// given location are the provided string. Will return an iterator on the
// first character after the expectedString.
// Arguments:
// - tb: the buffer who's content we should check
// - expectedString: The characters we're expecting
// - pos: the starting position in the buffer to check the contents of
// Return Value:
// - an iterator on the first character after the expectedString.
TextBufferCellIterator _verifyExpectedString(const TextBuffer& tb,
std::wstring_view expectedString,
const COORD pos)
{
auto iter = tb.GetCellDataAt(pos);
_verifyExpectedString(expectedString, iter);
return iter;
}
void ConptyRoundtripTests::ConptyOutputTestCanary()
{
Log::Comment(NoThrowString().Format(
L"This is a simple test to make sure that everything is working as expected."));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
_flushFirstFrame();
}
void ConptyRoundtripTests::SimpleWriteOutputTest()
{
Log::Comment(NoThrowString().Format(
L"Write some simple output, and make sure it gets rendered largely "
L"unmodified to the terminal"));
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();
expectedOutput.push_back("Hello World");
hostSm.ProcessString(L"Hello World");
VERIFY_SUCCEEDED(renderer.PaintFrame());
_verifyExpectedString(termTb, L"Hello World ", { 0, 0 });
}
void ConptyRoundtripTests::WriteTwoLinesUsesNewline()
{
Log::Comment(NoThrowString().Format(
L"Write two lines of output. We should use \r\n to move the cursor"));
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& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
hostSm.ProcessString(L"AAA");
hostSm.ProcessString(L"\x1b[2;1H");
hostSm.ProcessString(L"BBB");
auto verifyData = [](TextBuffer& tb) {
_verifyExpectedString(tb, L"AAA", { 0, 0 });
_verifyExpectedString(tb, L"BBB", { 0, 1 });
};
verifyData(hostTb);
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyData(termTb);
}
void ConptyRoundtripTests::WriteAFewSimpleLines()
{
Log::Comment(NoThrowString().Format(
L"Write more lines of outout. We should use \r\n to move the cursor"));
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& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
hostSm.ProcessString(L"AAA\n");
hostSm.ProcessString(L"BBB\n");
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"CCC");
auto verifyData = [](TextBuffer& tb) {
_verifyExpectedString(tb, L"AAA", { 0, 0 });
_verifyExpectedString(tb, L"BBB", { 0, 1 });
_verifyExpectedString(tb, L" ", { 0, 2 });
_verifyExpectedString(tb, L"CCC", { 0, 3 });
};
verifyData(hostTb);
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
expectedOutput.push_back("\r\n");
// Here, we're going to emit 3 spaces. The region that got invalidated was a
// rectangle from 0,0 to 3,3, so the vt renderer will try to render the
// region in between BBB and CCC as well, because it got included in the
// rectangle Or() operation.
// This behavior should not be seen as binding - if a future optimization
// breaks this test, it wouldn't be the worst.
expectedOutput.push_back(" ");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("CCC");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyData(termTb);
}

View File

@@ -18,7 +18,6 @@
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="TerminalApiTest.cpp" />
<ClCompile Include="ConptyRoundtripTests.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj">
@@ -39,42 +38,6 @@
<ProjectReference Include="..\TerminalCore\lib\TerminalCore-lib.vcxproj">
<Project>{ca5cad1a-abcd-429c-b551-8562ec954746}</Project>
</ProjectReference>
<!-- The following are all Console Host (host.lib) dependencies. We're
including them for the ConptyRoundtripTests, which instantiate a console
host, then user the output from conpty to dump directly into a Terminal,
and make sure the buffer contents align. -->
<ProjectReference Include="..\..\renderer\vt\ut_lib\vt.unittest.vcxproj">
<Project>{990F2657-8580-4828-943F-5DD657D11843}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)\src\host\ut_lib\host.unittest.vcxproj">
<Project>{06ec74cb-9a12-429c-b551-8562ec954746}</Project>
</ProjectReference>
<ProjectReference Include="..\..\propslib\propslib.vcxproj">
<Project>{345fd5a4-b32b-4f29-bd1c-b033bd2c35cc}</Project>
</ProjectReference>
<ProjectReference Include="..\..\interactivity\base\lib\InteractivityBase.vcxproj">
<Project>{06ec74cb-9a12-429c-b551-8562ec964846}</Project>
</ProjectReference>
<ProjectReference Include="..\..\interactivity\win32\lib\win32.LIB.vcxproj">
<Project>{06ec74cb-9a12-429c-b551-8532ec964726}</Project>
</ProjectReference>
<ProjectReference Include="..\..\tsf\tsf.vcxproj">
<Project>{2fd12fbb-1ddb-46d8-b818-1023c624caca}</Project>
</ProjectReference>
<ProjectReference Include="..\..\server\lib\server.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
</ProjectReference>
<ProjectReference Include="..\..\terminal\adapter\lib\adapter.vcxproj">
<Project>{dcf55140-ef6a-4736-a403-957e4f7430bb}</Project>
</ProjectReference>
<ProjectReference Include="..\..\internal\internal.vcxproj">
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="MockTermSettings.h" />
@@ -82,7 +45,7 @@
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";$(OpenConsoleDir)\src\host;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
@@ -92,4 +55,4 @@
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
</Project>
</Project>

View File

@@ -6,10 +6,8 @@ Module Name:
- precomp.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).
- 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).
Author(s):
- Carlos Zamora (cazamor) April 2019
@@ -17,18 +15,6 @@ Author(s):
#pragma once
// <Conhost includes>
// This header and define are needed so that the console host code can build in
// this test binary.
// Block minwindef.h min/max macros to prevent <algorithm> conflict
#define NOMINMAX
// This includes a lot of common headers needed by both the host and the propsheet
// including: windows.h, winuser, ntstatus, assert, and the DDK
#include "HostAndPropsheetIncludes.h"
// </Conhost Includes>
// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"
@@ -44,4 +30,4 @@ Author(s):
#ifdef CON_BUILD_PUBLIC
#define CON_USERPRIVAPI_INDIRECT
#define CON_DPIAPI_INDIRECT
#endif
#endif

View File

@@ -86,7 +86,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\chromium;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;$(OpenConsoleDir)\dep\wil\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;$(OpenConsoleDir)\dep\wil\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile>
<!-- Manually include the generated TerminalCore header's path, because

View File

@@ -1,7 +1,4 @@
#pragma once
#define NOMINMAX
#include <windows.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>

View File

@@ -82,7 +82,7 @@
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\chromium;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<RuntimeTypeInfo>false</RuntimeTypeInfo>

View File

@@ -5,11 +5,11 @@
<PreprocessorDefinitions>UNIT_TESTING;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets" Condition="Exists('$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets')" />
<Import Project="$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets" Condition="Exists('$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets'))" />
<Error Condition="!Exists('$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets'))" />
</Target>
</Project>

View File

@@ -27,7 +27,7 @@ using namespace Microsoft::Console::Types;
using Microsoft::Console::Interactivity::ServiceLocator;
using Microsoft::Console::VirtualTerminal::StateMachine;
// Used by WriteCharsLegacy.
#define IS_GLYPH_CHAR(wch) (((wch) >= L' ') && ((wch) != 0x007F))
#define IS_GLYPH_CHAR(wch) (((wch) < L' ') || ((wch) == 0x007F))
constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
@@ -395,7 +395,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
#pragma prefast(suppress : 26019, "Buffer is taken in multiples of 2. Validation is ok.")
const wchar_t Char = *lpString;
const wchar_t RealUnicodeChar = *pwchRealUnicode;
if (IS_GLYPH_CHAR(RealUnicodeChar) || fUnprocessed)
if (!IS_GLYPH_CHAR(RealUnicodeChar) || fUnprocessed)
{
if (IsGlyphFullWidth(Char))
{

View File

@@ -250,7 +250,7 @@ void BufferTests::ChafaGifPerformance()
for (DWORD pos = 0; pos < res_size; pos += 1000)
{
DWORD written = 0;
WriteConsoleA(Out, res_data + pos, std::min<DWORD>(1000, res_size - pos), &written, nullptr);
WriteConsoleA(Out, res_data + pos, min(1000, res_size - pos), &written, nullptr);
count++;
}

View File

@@ -477,7 +477,7 @@ void DimensionsTests::TestSetConsoleScreenBufferInfoEx()
}
// 2b. Do the comparison. Y should be correct, but X will be the lesser of the size we asked for or the window limit for word wrap.
if (sbiex.dwSize.Y == sbiexAfter.dwSize.Y && std::min(sbiex.dwSize.X, sWidthLimit) == sbiexAfter.dwSize.X)
if (sbiex.dwSize.Y == sbiexAfter.dwSize.Y && min(sbiex.dwSize.X, sWidthLimit) == sbiexAfter.dwSize.X)
{
fBufferSizePassed = true;
}

View File

@@ -237,10 +237,10 @@ void OutputTests::WriteConsoleOutputWWithClipping()
adjustedRegion.Bottom += bufferSize.Y / 2;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
expectedRegion.Left = max(0, adjustedRegion.Left);
expectedRegion.Top = max(0, adjustedRegion.Top);
expectedRegion.Right = min(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = min(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API and confirm results.
auto affected = adjustedRegion;
@@ -324,10 +324,10 @@ void OutputTests::WriteConsoleOutputWNegativePositions()
adjustedRegion.Bottom -= 10;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
expectedRegion.Left = max(0, adjustedRegion.Left);
expectedRegion.Top = max(0, adjustedRegion.Top);
expectedRegion.Right = min(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = min(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API and confirm results.
auto affected = adjustedRegion;
@@ -758,10 +758,10 @@ void OutputTests::ReadConsoleOutputWWithClipping()
adjustedRegion.Bottom += bufferSize.Y / 2;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
expectedRegion.Left = max(0, adjustedRegion.Left);
expectedRegion.Top = max(0, adjustedRegion.Top);
expectedRegion.Right = min(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = min(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API and confirm results.
// NOTE: We expect this to be broken for v1. It's always been wrong there (returning a clipped count of bytes instead of the whole rectangle).
@@ -852,10 +852,10 @@ void OutputTests::ReadConsoleOutputWNegativePositions()
adjustedRegion.Bottom -= 10;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
expectedRegion.Left = max(0, adjustedRegion.Left);
expectedRegion.Top = max(0, adjustedRegion.Top);
expectedRegion.Right = min(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = min(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API
// NOTE: Due to the same reason as the ReadConsoleOutputWWithClipping test (the v1 buffer told the driver the wrong return buffer byte length)

View File

@@ -91,7 +91,7 @@ void TestGetConsoleTitleWPrepExpectedHelper(_In_reads_(cchTitle) const wchar_t*
TestGetConsoleTitleWFillHelper(wchReadExpected, cchReadExpected, L'Z');
// Prep expected data
size_t const cchCopy = std::min(cchTitle, cchTryToRead);
size_t const cchCopy = min(cchTitle, cchTryToRead);
VERIFY_SUCCEEDED(StringCchCopyNW(wchReadExpected, cchReadBuffer, wchTitle, cchCopy - 1)); // Copy as much room as we said we had leaving space for null terminator
}

View File

@@ -2321,7 +2321,7 @@ void ReadStringWithReadConsoleInputAHelper(HANDLE hIn, PCSTR pszExpectedText, si
while (cchRead < cchExpectedText)
{
// expected read is either the size of the buffer or the number of characters remaining, whichever is smaller.
DWORD const dwReadExpected = (DWORD)std::min(cbBuffer, cchExpectedText - cchRead);
DWORD const dwReadExpected = (DWORD)min(cbBuffer, cchExpectedText - cchRead);
DWORD dwRead;
if (!VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputA(hIn, irRead, (DWORD)cbBuffer, &dwRead), L"Attempt to read input into buffer."))

View File

@@ -3,8 +3,6 @@
#pragma once
#define NOMINMAX
#include "windows.h"
#include "wincon.h"
#include "windowsx.h"

View File

@@ -53,7 +53,7 @@
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="TE.Managed, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Taef.Redist.Wlk.10.48.200103003-develop\lib\net45\TE.Managed.dll</HintPath>
<HintPath>..\..\..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\lib\net45\TE.Managed.dll</HintPath>
</Reference>
<Reference Include="UIAutomationClient" />
<Reference Include="UIAutomationTypes" />
@@ -64,10 +64,10 @@
<HintPath>..\..\..\packages\Selenium.Support.3.5.0\lib\net40\WebDriver.Support.dll</HintPath>
</Reference>
<Reference Include="Wex.Common.Managed, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Taef.Redist.Wlk.10.48.200103003-develop\lib\net45\Wex.Common.Managed.dll</HintPath>
<HintPath>..\..\..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\lib\net45\Wex.Common.Managed.dll</HintPath>
</Reference>
<Reference Include="Wex.Logger.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Taef.Redist.Wlk.10.48.200103003-develop\lib\net45\Wex.Logger.Interop.dll</HintPath>
<HintPath>..\..\..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\lib\net45\Wex.Logger.Interop.dll</HintPath>
</Reference>
<Reference Include="WindowsBase" />
</ItemGroup>
@@ -142,11 +142,11 @@
<PropertyGroup>
<PostBuildEvent>copy $(SolutionDir)\dep\WinAppDriver\* $(OutDir)\</PostBuildEvent>
</PropertyGroup>
<Import Project="..\..\..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets" Condition="Exists('..\..\..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets')" />
<Import Project="..\..\..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets" Condition="Exists('..\..\..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Taef.Redist.Wlk.10.48.200103003-develop\build\Taef.Redist.Wlk.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Taef.Redist.Wlk.targets'))" />
</Target>
</Project>

View File

@@ -5,5 +5,5 @@
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" />
<package id="Selenium.Support" version="3.5.0" targetFramework="net45" />
<package id="Selenium.WebDriver" version="3.5.0" targetFramework="net45" />
<package id="Taef.Redist.Wlk" version="10.48.200103003-develop" targetFramework="net45" />
<package id="Taef.Redist.Wlk" version="10.38.190610001-uapadmin" targetFramework="net45" />
</packages>

View File

@@ -1347,35 +1347,6 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
return Status;
}
// Routine Description:
// - A private API call for performing a line feed, possibly preceded by carriage return.
// Moves the cursor down one line, and possibly also to the leftmost column.
// Parameters:
// - screenInfo - A pointer to the screen buffer that should perform the line feed.
// - withReturn - Set to true if a carriage return should be performed as well.
// Return value:
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error.
[[nodiscard]] NTSTATUS DoSrvPrivateLineFeed(SCREEN_INFORMATION& screenInfo, const bool withReturn)
{
auto& textBuffer = screenInfo.GetTextBuffer();
auto cursorPosition = textBuffer.GetCursor().GetPosition();
// We turn the cursor on before an operation that might scroll the viewport, otherwise
// that can result in an old copy of the cursor being left behind on the screen.
textBuffer.GetCursor().SetIsOn(true);
// Since we are explicitly moving down a row, clear the wrap status on the row we're leaving
textBuffer.GetRowByOffset(cursorPosition.Y).GetCharRow().SetWrapForced(false);
cursorPosition.Y += 1;
if (withReturn)
{
cursorPosition.X = 0;
}
return AdjustCursorPosition(screenInfo, cursorPosition, FALSE, nullptr);
}
// Routine Description:
// - A private API call for performing a "Reverse line feed", essentially, the opposite of '\n'.
// Moves the cursor up one line, and tries to keep its position in the line
@@ -2057,18 +2028,13 @@ void DoSrvPrivateModifyLinesImpl(const size_t count, const bool insert)
coordDestination.X = 0;
if (insert)
{
coordDestination.Y = cursorPosition.Y + base::MakeClampedNum(count);
coordDestination.Y = (cursorPosition.Y) + gsl::narrow<short>(count);
}
else
{
coordDestination.Y = (cursorPosition.Y) - base::MakeClampedNum(count);
coordDestination.Y = (cursorPosition.Y) - gsl::narrow<short>(count);
}
// The destination needs to still be inside the buffer.
// We will take anything that is "too big" of a line modification and clamp it down to
// the maximum modification that is possible in our buffer size.
screenInfo.GetBufferSize().Clamp(coordDestination);
// Note the revealed lines are filled with the standard erase attributes.
LOG_IF_FAILED(DoSrvPrivateScrollRegion(screenInfo,
srScroll,

View File

@@ -33,7 +33,6 @@ void DoSrvPrivateShowCursor(SCREEN_INFORMATION& screenInfo, const bool show) noe
void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool fEnable);
[[nodiscard]] NTSTATUS DoSrvPrivateSetScrollingRegion(SCREEN_INFORMATION& screenInfo, const SMALL_RECT& scrollMargins);
[[nodiscard]] NTSTATUS DoSrvPrivateLineFeed(SCREEN_INFORMATION& screenInfo, const bool withReturn);
[[nodiscard]] NTSTATUS DoSrvPrivateReverseLineFeed(SCREEN_INFORMATION& screenInfo);
[[nodiscard]] NTSTATUS DoSrvPrivateUseAlternateScreenBuffer(SCREEN_INFORMATION& screenInfo);

View File

@@ -386,40 +386,6 @@ bool ConhostInternalGetSet::PrivateSetScrollingRegion(const SMALL_RECT& scrollMa
return NT_SUCCESS(DoSrvPrivateSetScrollingRegion(_io.GetActiveOutputBuffer(), scrollMargins));
}
// Method Description:
// - Retrieves the current Line Feed/New Line (LNM) mode.
// Arguments:
// - None
// Return Value:
// - true if a line feed also produces a carriage return. false otherwise.
bool ConhostInternalGetSet::PrivateGetLineFeedMode() const
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.IsReturnOnNewlineAutomatic();
}
// Routine Description:
// - Connects the PrivateLineFeed call directly into our Driver Message servicing call inside Conhost.exe
// PrivateLineFeed is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on our public API surface.
// Arguments:
// - withReturn - Set to true if a carriage return should be performed as well.
// Return Value:
// - true if successful (see DoSrvPrivateLineFeed). false otherwise.
bool ConhostInternalGetSet::PrivateLineFeed(const bool withReturn)
{
return NT_SUCCESS(DoSrvPrivateLineFeed(_io.GetActiveOutputBuffer(), withReturn));
}
// Routine Description:
// - Sends a notify message to play the "SystemHand" sound event.
// Return Value:
// - true if successful. false otherwise.
bool ConhostInternalGetSet::PrivateWarningBell()
{
return _io.GetActiveOutputBuffer().SendNotifyBeep();
}
// Routine Description:
// - Connects the PrivateReverseLineFeed call directly into our Driver Message servicing call inside Conhost.exe
// PrivateReverseLineFeed is an internal-only "API" call that the vt commands can execute,

View File

@@ -98,10 +98,6 @@ public:
bool PrivateSetScrollingRegion(const SMALL_RECT& scrollMargins) override;
bool PrivateWarningBell() override;
bool PrivateGetLineFeedMode() const override;
bool PrivateLineFeed(const bool withReturn) override;
bool PrivateReverseLineFeed() override;
bool SetConsoleTitleW(const std::wstring_view title) override;

View File

@@ -1425,21 +1425,224 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
// Save cursor's relative height versus the viewport
SHORT const sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().Y - _viewport.Top();
HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get());
Cursor& oldCursor = _textBuffer->GetCursor();
Cursor& newCursor = newTextBuffer->GetCursor();
// skip any drawing updates that might occur as we manipulate the new buffer
oldCursor.StartDeferDrawing();
newCursor.StartDeferDrawing();
if (SUCCEEDED(hr))
// We need to save the old cursor position so that we can
// place the new cursor back on the equivalent character in
// the new buffer.
COORD cOldCursorPos = oldCursor.GetPosition();
COORD cOldLastChar = _textBuffer->GetLastNonSpaceCharacter();
short const cOldRowsTotal = cOldLastChar.Y + 1;
short const cOldColsTotal = GetBufferSize().Width();
COORD cNewCursorPos = { 0 };
bool fFoundCursorPos = false;
NTSTATUS status = STATUS_SUCCESS;
// Loop through all the rows of the old buffer and reprint them into the new buffer
for (short iOldRow = 0; iOldRow < cOldRowsTotal; iOldRow++)
{
// Fetch the row and its "right" which is the last printable character.
const ROW& Row = _textBuffer->GetRowByOffset(iOldRow);
const CharRow& charRow = Row.GetCharRow();
short iRight = static_cast<short>(charRow.MeasureRight());
// There is a special case here. If the row has a "wrap"
// flag on it, but the right isn't equal to the width (one
// index past the final valid index in the row) then there
// were a bunch trailing of spaces in the row.
// (But the measuring functions for each row Left/Right do
// not count spaces as "displayable" so they're not
// included.)
// As such, adjust the "right" to be the width of the row
// to capture all these spaces
if (charRow.WasWrapForced())
{
iRight = cOldColsTotal;
// And a combined special case.
// If we wrapped off the end of the row by adding a
// piece of padding because of a double byte LEADING
// character, then remove one from the "right" to
// leave this padding out of the copy process.
if (charRow.WasDoubleBytePadded())
{
iRight--;
}
}
// Loop through every character in the current row (up to
// the "right" boundary, which is one past the final valid
// character)
for (short iOldCol = 0; iOldCol < iRight; iOldCol++)
{
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
}
try
{
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto glyph = Row.GetCharRow().GlyphAt(iOldCol);
const auto dbcsAttr = Row.GetCharRow().DbcsAttrAt(iOldCol);
const auto textAttr = Row.GetAttrRow().GetAttrByColumn(iOldCol);
if (!newTextBuffer->InsertCharacter(glyph, dbcsAttr, textAttr))
{
status = STATUS_NO_MEMORY;
break;
}
}
catch (...)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
}
if (NT_SUCCESS(status))
{
// If we didn't have a full row to copy, insert a new
// line into the new buffer.
// Only do so if we were not forced to wrap. If we did
// force a word wrap, then the existing line break was
// only because we ran out of space.
if (iRight < cOldColsTotal && !charRow.WasWrapForced())
{
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
}
// Only do this if it's not the final line in the buffer.
// On the final line, we want the cursor to sit
// where it is done printing for the cursor
// adjustment to follow.
if (iOldRow < cOldRowsTotal - 1)
{
status = newTextBuffer->NewlineCursor() ? status : STATUS_NO_MEMORY;
}
else
{
// If we are on the final line of the buffer, we have one more check.
// We got into this code path because we are at the right most column of a row in the old buffer
// that had a hard return (no wrap was forced).
// However, as we're inserting, the old row might have just barely fit into the new buffer and
// caused a new soft return (wrap was forced) putting the cursor at x=0 on the line just below.
// We need to preserve the memory of the hard return at this point by inserting one additional
// hard newline, otherwise we've lost that information.
// We only do this when the cursor has just barely poured over onto the next line so the hard return
// isn't covered by the soft one.
// e.g.
// The old line was:
// |aaaaaaaaaaaaaaaaaaa | with no wrap which means there was a newline after that final a.
// The cursor was here ^
// And the new line will be:
// |aaaaaaaaaaaaaaaaaaa| and show a wrap at the end
// | |
// ^ and the cursor is now there.
// If we leave it like this, we've lost the newline information.
// So we insert one more newline so a continued reflow of this buffer by resizing larger will
// continue to look as the original output intended with the newline data.
// After this fix, it looks like this:
// |aaaaaaaaaaaaaaaaaaa| no wrap at the end (preserved hard newline)
// | |
// ^ and the cursor is now here.
const COORD coordNewCursor = newCursor.GetPosition();
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
{
if (newTextBuffer->GetRowByOffset(coordNewCursor.Y - 1).GetCharRow().WasWrapForced())
{
status = newTextBuffer->NewlineCursor() ? status : STATUS_NO_MEMORY;
}
}
}
}
}
}
if (NT_SUCCESS(status))
{
// Finish copying remaining parameters from the old text buffer to the new one
newTextBuffer->CopyProperties(*_textBuffer);
// If we found where to put the cursor while placing characters into the buffer,
// just put the cursor there. Otherwise we have to advance manually.
if (fFoundCursorPos)
{
newCursor.SetPosition(cNewCursorPos);
}
else
{
// Advance the cursor to the same offset as before
// get the number of newlines and spaces between the old end of text and the old cursor,
// then advance that many newlines and chars
int iNewlines = cOldCursorPos.Y - cOldLastChar.Y;
int iIncrements = cOldCursorPos.X - cOldLastChar.X;
const COORD cNewLastChar = newTextBuffer->GetLastNonSpaceCharacter();
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
// because the cursor is already on the next line
if (newTextBuffer->GetRowByOffset(cNewLastChar.Y).GetCharRow().WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
else
{
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
// old buffer will be one more than in this buffer, so new need one LESS.
if (_textBuffer->GetRowByOffset(cOldLastChar.Y).GetCharRow().WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
}
for (int r = 0; r < iNewlines; r++)
{
if (!newTextBuffer->NewlineCursor())
{
status = STATUS_NO_MEMORY;
break;
}
}
if (NT_SUCCESS(status))
{
for (int c = 0; c < iIncrements - 1; c++)
{
if (!newTextBuffer->IncrementCursor())
{
status = STATUS_NO_MEMORY;
break;
}
}
}
}
}
if (NT_SUCCESS(status))
{
Cursor& newCursor = newTextBuffer->GetCursor();
// Adjust the viewport so the cursor doesn't wildly fly off up or down.
SHORT const sCursorHeightInViewportAfter = newCursor.GetPosition().Y - _viewport.Top();
COORD coordCursorHeightDiff = { 0 };
coordCursorHeightDiff.Y = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
LOG_IF_FAILED(SetViewportOrigin(false, coordCursorHeightDiff, true));
_textBuffer.swap(newTextBuffer);
}
// Save old cursor size before we delete it
ULONG const ulSize = oldCursor.GetSize();
return NTSTATUS_FROM_HRESULT(hr);
_textBuffer.swap(newTextBuffer);
// Set size back to real size as it will be taking over the rendering duties.
newCursor.SetSize(ulSize);
newCursor.EndDeferDrawing();
}
oldCursor.EndDeferDrawing();
return status;
}
//

View File

@@ -307,7 +307,5 @@ private:
friend class TextBufferIteratorTests;
friend class ScreenBufferTests;
friend class CommonState;
friend class ConptyOutputTests;
friend class ConptyRoundtripTests;
#endif
};

View File

@@ -1,309 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/vt/Xterm256Engine.hpp"
#include "../../renderer/vt/XtermEngine.hpp"
#include "../../renderer/vt/WinTelnetEngine.hpp"
#include "../Settings.hpp"
#include "CommonState.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::VirtualTerminal;
using namespace Microsoft::Console;
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
class ConptyOutputTests
{
TEST_CLASS(ConptyOutputTests);
TEST_CLASS_SETUP(ClassSetup)
{
m_state = std::make_unique<CommonState>();
m_state->InitEvents();
m_state->PrepareGlobalFont();
m_state->PrepareGlobalScreenBuffer();
m_state->PrepareGlobalInputBuffer();
return true;
}
TEST_CLASS_CLEANUP(ClassCleanup)
{
m_state->CleanupGlobalScreenBuffer();
m_state->CleanupGlobalFont();
m_state->CleanupGlobalInputBuffer();
m_state.release();
return true;
}
TEST_METHOD_SETUP(MethodSetup)
{
// Set up some sane defaults
auto& g = ServiceLocator::LocateGlobals();
auto& gci = g.getConsoleInformation();
gci.SetDefaultForegroundColor(INVALID_COLOR);
gci.SetDefaultBackgroundColor(INVALID_COLOR);
gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
m_state->PrepareNewTextBufferInfo(true);
auto& currentBuffer = gci.GetActiveOutputBuffer();
// Make sure a test hasn't left us in the alt buffer on accident
VERIFY_IS_FALSE(currentBuffer._IsAltBuffer());
VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
VERIFY_ARE_EQUAL(COORD({ 0, 0 }), currentBuffer.GetTextBuffer().GetCursor().GetPosition());
g.pRender = new Renderer(&gci.renderData, nullptr, 0, nullptr);
// Set up an xterm-256 renderer for conpty
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
Viewport initialViewport = currentBuffer.GetViewport();
_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hFile),
gci,
initialViewport,
gci.GetColorTable(),
static_cast<WORD>(gci.GetColorTableSize()));
auto pfn = std::bind(&ConptyOutputTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
_pVtRenderEngine->SetTestCallback(pfn);
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
gci.GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
expectedOutput.clear();
return true;
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
m_state->CleanupNewTextBufferInfo();
auto& g = ServiceLocator::LocateGlobals();
delete g.pRender;
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
return true;
}
TEST_METHOD(ConptyOutputTestCanary);
TEST_METHOD(SimpleWriteOutputTest);
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);
private:
bool _writeCallback(const char* const pch, size_t const cch);
void _flushFirstFrame();
std::deque<std::string> expectedOutput;
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
std::unique_ptr<CommonState> m_state;
};
bool ConptyOutputTests::_writeCallback(const char* const pch, size_t const cch)
{
std::string actualString = std::string(pch, cch);
VERIFY_IS_GREATER_THAN(expectedOutput.size(),
static_cast<size_t>(0),
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()));
std::string first = expectedOutput.front();
expectedOutput.pop_front();
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
VERIFY_ARE_EQUAL(first.length(), cch);
VERIFY_ARE_EQUAL(first, actualString);
return true;
}
void ConptyOutputTests::_flushFirstFrame()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
expectedOutput.push_back("\x1b[2J");
expectedOutput.push_back("\x1b[m");
expectedOutput.push_back("\x1b[H"); // Go Home
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
// Function Description:
// - Helper function to validate that a number of characters in a row are all
// the same. Validates that the next end-start characters are all equal to the
// provided string. Will move the provided iterator as it validates. The
// caller should ensure that `iter` starts where they would like to validate.
// Arguments:
// - expectedChar: The character (or characters) we're expecting
// - iter: a iterator pointing to the cell we'd like to start validating at.
// - start: the first index in the range we'd like to validate
// - end: the last index in the range we'd like to validate
// Return Value:
// - <none>
void _verifySpanOfText(const wchar_t* const expectedChar,
TextBufferCellIterator& iter,
const int start,
const int end)
{
for (int x = start; x < end; x++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
if (iter->Chars() != expectedChar)
{
Log::Comment(NoThrowString().Format(L"character [%d] was mismatched", x));
}
VERIFY_ARE_EQUAL(expectedChar, (iter++)->Chars());
}
Log::Comment(NoThrowString().Format(
L"Successfully validated %d characters were '%s'", end - start, expectedChar));
}
void ConptyOutputTests::ConptyOutputTestCanary()
{
Log::Comment(NoThrowString().Format(
L"This is a simple test to make sure that everything is working as expected."));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
_flushFirstFrame();
}
void ConptyOutputTests::SimpleWriteOutputTest()
{
Log::Comment(NoThrowString().Format(
L"Write some simple output, and make sure it gets rendered largely "
L"unmodified to the terminal"));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
_flushFirstFrame();
expectedOutput.push_back("Hello World");
sm.ProcessString(L"Hello World");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::WriteTwoLinesUsesNewline()
{
Log::Comment(NoThrowString().Format(
L"Write two lines of output. We should use \r\n to move the cursor"));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& tb = si.GetTextBuffer();
_flushFirstFrame();
sm.ProcessString(L"AAA");
sm.ProcessString(L"\x1b[2;1H");
sm.ProcessString(L"BBB");
{
auto iter = tb.GetCellDataAt({ 0, 0 });
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 1 });
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
}
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::WriteAFewSimpleLines()
{
Log::Comment(NoThrowString().Format(
L"Write more lines of output. We should use \r\n to move the cursor"));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& tb = si.GetTextBuffer();
_flushFirstFrame();
sm.ProcessString(L"AAA\n");
sm.ProcessString(L"BBB\n");
sm.ProcessString(L"\n");
sm.ProcessString(L"CCC");
{
auto iter = tb.GetCellDataAt({ 0, 0 });
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 1 });
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 2 });
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 3 });
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
}
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
expectedOutput.push_back("\r\n");
// Here, we're going to emit 3 spaces. The region that got invalidated was a
// rectangle from 0,0 to 3,3, so the vt renderer will try to render the
// region in between BBB and CCC as well, because it got included in the
// rectangle Or() operation.
// This behavior should not be seen as binding - if a future optimization
// breaks this test, it wouldn't be the worst.
expectedOutput.push_back(" ");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("CCC");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}

View File

@@ -40,7 +40,6 @@
<ClCompile Include="ViewportTests.cpp" />
<ClCompile Include="VtIoTests.cpp" />
<ClCompile Include="VtRendererTests.cpp" />
<ClCompile Include="ConptyOutputTests.cpp" />
<Clcompile Include="..\..\types\IInputEventStreams.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>

View File

@@ -178,8 +178,6 @@ class ScreenBufferTests
TEST_METHOD(DeleteLinesInMargins);
TEST_METHOD(ReverseLineFeedInMargins);
TEST_METHOD(LineFeedEscapeSequences);
TEST_METHOD(ScrollLines256Colors);
TEST_METHOD(SetOriginMode);
@@ -4268,8 +4266,6 @@ void ScreenBufferTests::ReverseLineFeedInMargins()
_CommonScrollingSetup();
// Set the top scroll margin to the top of the screen
stateMachine.ProcessString(L"\x1b[1;5r");
// Make sure we clear the margins on exit so they can't break other tests.
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });
// Move to column 5 of line 1, the top of the screen
stateMachine.ProcessString(L"\x1b[1;5H");
// Execute a reverse line feed (RI)
@@ -4298,91 +4294,6 @@ void ScreenBufferTests::ReverseLineFeedInMargins()
}
}
void ScreenBufferTests::LineFeedEscapeSequences()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:withReturn", L"{true, false}")
END_TEST_METHOD_PROPERTIES()
bool withReturn;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"withReturn", withReturn));
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
auto& stateMachine = si.GetStateMachine();
auto& cursor = si.GetTextBuffer().GetCursor();
std::wstring escapeSequence;
if (withReturn)
{
Log::Comment(L"Testing line feed with carriage return (NEL).");
escapeSequence = L"\033E";
}
else
{
Log::Comment(L"Testing line feed without carriage return (IND).");
escapeSequence = L"\033D";
}
// Set the viewport to a reasonable size.
const auto view = Viewport::FromDimensions({ 0, 0 }, { 80, 25 });
si.SetViewport(view, true);
// We'll place the cursor in the center of the line.
// If we are performing a line feed with carriage return,
// the cursor should move to the leftmost column.
const short initialX = view.Width() / 2;
const short expectedX = withReturn ? 0 : initialX;
{
Log::Comment(L"Starting at the top of viewport");
const short initialY = 0;
const short expectedY = initialY + 1;
const short expectedViewportTop = si.GetViewport().Top();
cursor.SetPosition(COORD{ initialX, initialY });
stateMachine.ProcessString(escapeSequence);
VERIFY_ARE_EQUAL(expectedX, cursor.GetPosition().X);
VERIFY_ARE_EQUAL(expectedY, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(expectedViewportTop, si.GetViewport().Top());
}
{
Log::Comment(L"Starting at the bottom of viewport");
const short initialY = si.GetViewport().BottomInclusive();
const short expectedY = initialY + 1;
const short expectedViewportTop = si.GetViewport().Top() + 1;
cursor.SetPosition(COORD{ initialX, initialY });
stateMachine.ProcessString(escapeSequence);
VERIFY_ARE_EQUAL(expectedX, cursor.GetPosition().X);
VERIFY_ARE_EQUAL(expectedY, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(expectedViewportTop, si.GetViewport().Top());
}
{
Log::Comment(L"Starting at the bottom of the scroll margins");
// Set the margins to rows 5 to 10.
stateMachine.ProcessString(L"\x1b[5;10r");
// Make sure we clear the margins on exit so they can't break other tests.
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });
const short initialY = si.GetViewport().Top() + 9;
const short expectedY = initialY;
const short expectedViewportTop = si.GetViewport().Top();
_FillLine(initialY, L'Q', {});
cursor.SetPosition(COORD{ initialX, initialY });
stateMachine.ProcessString(escapeSequence);
VERIFY_ARE_EQUAL(expectedX, cursor.GetPosition().X);
VERIFY_ARE_EQUAL(expectedY, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(expectedViewportTop, si.GetViewport().Top());
// Verify the line of Qs has been scrolled up.
VERIFY_IS_TRUE(_ValidateLineContains(initialY - 1, L'Q', {}));
VERIFY_IS_TRUE(_ValidateLineContains(initialY, L' ', si.GetAttributes()));
}
}
void ScreenBufferTests::ScrollLines256Colors()
{
BEGIN_TEST_METHOD_PROPERTIES()

View File

@@ -149,7 +149,7 @@ class TextBufferTests
void TextBufferTests::TestBufferCreate()
{
VERIFY_SUCCEEDED(m_state->GetTextBufferInfoInitResult());
VERIFY_SUCCESS_NTSTATUS(m_state->GetTextBufferInfoInitResult());
}
TextBuffer& TextBufferTests::GetTbi()

View File

@@ -36,7 +36,6 @@ SOURCES = \
InputBufferTests.cpp \
VtIoTests.cpp \
VtRendererTests.cpp \
ConptyOutputTests.cpp \
ViewportTests.cpp \
ConsoleArgumentsTests.cpp \
CommandLineTests.cpp \

View File

@@ -68,12 +68,6 @@
// CppCoreCheck
#include <CppCoreCheck/Warnings.h>
// Chromium Numerics (safe math)
#pragma warning(push)
#pragma warning(disable:4100)
#include <base/numerics/safe_math.h>
#pragma warning(pop)
// IntSafe
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
#include <intsafe.h>

View File

@@ -38,7 +38,7 @@ public:
CommonState() :
m_heap(GetProcessHeap()),
m_hrTextBufferInfo(E_FAIL),
m_ntstatusTextBufferInfo(STATUS_FAIL_CHECK),
m_pFontInfo(nullptr),
m_backupTextBufferInfo(),
m_readHandle(nullptr)
@@ -143,7 +143,7 @@ public:
gci.SetCookedReadData(nullptr);
}
void PrepareNewTextBufferInfo(const bool useDefaultAttributes = false)
void PrepareNewTextBufferInfo()
{
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordScreenBufferSize;
@@ -152,29 +152,26 @@ public:
UINT uiCursorSize = 12;
auto initialAttributes = useDefaultAttributes ? gci.GetDefaultAttributes() :
TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY };
m_backupTextBufferInfo.swap(gci.pCurrentScreenBuffer->_textBuffer);
try
{
std::unique_ptr<TextBuffer> textBuffer = std::make_unique<TextBuffer>(coordScreenBufferSize,
initialAttributes,
TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY },
uiCursorSize,
gci.pCurrentScreenBuffer->GetRenderTarget());
if (textBuffer.get() == nullptr)
{
m_hrTextBufferInfo = E_OUTOFMEMORY;
m_ntstatusTextBufferInfo = STATUS_NO_MEMORY;
}
else
{
m_hrTextBufferInfo = S_OK;
m_ntstatusTextBufferInfo = STATUS_SUCCESS;
}
gci.pCurrentScreenBuffer->_textBuffer.swap(textBuffer);
}
catch (...)
{
m_hrTextBufferInfo = wil::ResultFromCaughtException();
m_ntstatusTextBufferInfo = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
}
@@ -224,14 +221,14 @@ public:
textBuffer.GetCursor().SetYPosition(cRowsToFill);
}
[[nodiscard]] HRESULT GetTextBufferInfoInitResult()
[[nodiscard]] NTSTATUS GetTextBufferInfoInitResult()
{
return m_hrTextBufferInfo;
return m_ntstatusTextBufferInfo;
}
private:
HANDLE m_heap;
HRESULT m_hrTextBufferInfo;
NTSTATUS m_ntstatusTextBufferInfo;
FontInfo* m_pFontInfo;
std::unique_ptr<TextBuffer> m_backupTextBufferInfo;
std::unique_ptr<INPUT_READ_HANDLE_DATA> m_readHandle;

View File

@@ -245,21 +245,21 @@ BOOL UpdateStateInfo(HWND hDlg, UINT Item, int Value)
case IDD_WINDOW_POSX:
if (Value < 0)
{
gpStateInfo->WindowPosX = std::max(SHORT_MIN, Value);
gpStateInfo->WindowPosX = max(SHORT_MIN, Value);
}
else
{
gpStateInfo->WindowPosX = std::min(SHORT_MAX, Value);
gpStateInfo->WindowPosX = min(SHORT_MAX, Value);
}
break;
case IDD_WINDOW_POSY:
if (Value < 0)
{
gpStateInfo->WindowPosY = std::max(SHORT_MIN, Value);
gpStateInfo->WindowPosY = max(SHORT_MIN, Value);
}
else
{
gpStateInfo->WindowPosY = std::min(SHORT_MAX, Value);
gpStateInfo->WindowPosY = min(SHORT_MAX, Value);
}
break;
case IDD_AUTO_POSITION:
@@ -316,10 +316,10 @@ BOOL UpdateStateInfo(HWND hDlg, UINT Item, int Value)
gpStateInfo->InsertMode = Value;
break;
case IDD_HISTORY_SIZE:
gpStateInfo->HistoryBufferSize = std::max(Value, 1);
gpStateInfo->HistoryBufferSize = max(Value, 1);
break;
case IDD_HISTORY_NUM:
gpStateInfo->NumberOfHistoryBuffers = std::max(Value, 1);
gpStateInfo->NumberOfHistoryBuffers = max(Value, 1);
break;
case IDD_HISTORY_NODUP:
gpStateInfo->HistoryNoDup = Value;
@@ -646,7 +646,7 @@ INT_PTR ConsolePropertySheet(__in HWND hWnd, __in PCONSOLE_STATE_INFO pStateInfo
psh.hInstance = ghInstance;
psh.pszCaption = awchBuffer;
psh.nPages = g_fForceV2 ? NUMBER_OF_PAGES : V1_NUMBER_OF_PAGES;
psh.nStartPage = std::min<UINT>(gnCurrentPage, ARRAYSIZE(psp));
psh.nStartPage = min(gnCurrentPage, ARRAYSIZE(psp));
psh.ppsp = psp;
psh.pfnCallback = NULL;

View File

@@ -91,7 +91,7 @@ UINT GetItemHeight(const HWND hDlg)
SelectObject(hDC, hFont);
}
ReleaseDC(hDlg, hDC);
return std::max(tm.tmHeight, bmTT.bmHeight);
return max(tm.tmHeight, bmTT.bmHeight);
}
// The V1 console doesn't support arbitrary TTF fonts, so only allow the enumeration of all TT fonts in the conditions below:

View File

@@ -3,8 +3,6 @@
#pragma once
#define NOMINMAX
// -- WARNING -- LOAD BEARING CODE --
// This define ABSOLUTELY MUST be included (and equal to 1, or more specifically != 0)
// prior to the import of Common Controls.

View File

@@ -72,8 +72,8 @@ VOID
MinSize.y = (GetSystemMetrics(SM_CYMIN) - NonClientSize.y) / lpFont->Size.Y;
MaxSize.x = GetSystemMetrics(SM_CXFULLSCREEN) / lpFont->Size.X;
MaxSize.y = GetSystemMetrics(SM_CYFULLSCREEN) / lpFont->Size.Y;
WindowSize.x = std::max(MinSize.x, std::min<LONG>(MaxSize.x, gpStateInfo->WindowSize.X));
WindowSize.y = std::max(MinSize.y, std::min<LONG>(MaxSize.y, gpStateInfo->WindowSize.Y));
WindowSize.x = max(MinSize.x, min(MaxSize.x, gpStateInfo->WindowSize.X));
WindowSize.y = max(MinSize.y, min(MaxSize.y, gpStateInfo->WindowSize.Y));
/*
* Get the window rectangle, making sure it's at least twice the

View File

@@ -4,8 +4,6 @@
#define DEFINE_CONSOLEV2_PROPERTIES
#define INC_OLE2
#define NOMINMAX
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS

View File

@@ -151,12 +151,8 @@ Renderer::~Renderer()
void Renderer::_NotifyPaintFrame()
{
// If we're running in the unittests, we might not have a render thread.
if (_pThread)
{
// The thread will provide throttling for us.
_pThread->NotifyPaint();
}
// The thread will provide throttling for us.
_pThread->NotifyPaint();
}
// Routine Description:

View File

@@ -125,9 +125,5 @@ namespace Microsoft::Console::Render
// Helper functions to diagnose issues with painting and layout.
// These are only actually effective/on in Debug builds when the flag is set using an attached debugger.
bool _fDebug = false;
#ifdef UNIT_TESTING
friend class ConptyOutputTests;
#endif
};
}

View File

@@ -404,8 +404,30 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory
// Offsets is how far to move the origin (in pixels) from where it is
auto& offset = _glyphOffsets.at(i);
// Get how many columns we expected the glyph to have and mutiply into pixels.
const auto columns = _textClusterColumns.at(i);
// Get how many columns we expected the glyph to have and multiply into pixels.
UINT16 columns = 0;
{
// Because of typographic features such as ligatures, it is well possible for a glyph to represent
// multiple code points. Previous calls to IDWriteTextAnalyzer::GetGlyphs stores the mapping
// information between code points and glyphs in _glyphClusters.
// To properly allocate the columns for such glyphs, we need to find all characters that this glyph
// is representing and add column counts for all the characters together.
// Find the range for current glyph run in _glyphClusters.
const auto runStartIterator = _glyphClusters.begin() + run.textStart;
const auto runEndIterator = _glyphClusters.begin() + run.textStart + run.textLength;
// Find the range of characters that the current glyph is representing.
const auto firstIterator = std::find(runStartIterator, runEndIterator, i - run.glyphStart);
const auto lastIterator = std::find(runStartIterator, runEndIterator, i - run.glyphStart + 1);
// Add all allocated column counts together.
for (auto j = firstIterator; j < lastIterator; j++)
{
const auto charIndex = std::distance(_glyphClusters.begin(), j);
columns += _textClusterColumns.at(charIndex);
}
}
const auto advanceExpected = static_cast<float>(columns * _width);
// If what we expect is bigger than what we have... pad it out.

View File

@@ -46,7 +46,6 @@ namespace Microsoft::Console::Render
#ifdef UNIT_TESTING
friend class VtRendererTest;
friend class ConptyOutputTests;
#endif
};
}

View File

@@ -73,7 +73,6 @@ namespace Microsoft::Console::Render
#ifdef UNIT_TESTING
friend class VtRendererTest;
friend class ConptyOutputTests;
#endif
};
}

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