mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 14:50:55 +00:00
Compare commits
9 Commits
dev/miniks
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95cca5470e | ||
|
|
795fb69865 | ||
|
|
90157e30d3 | ||
|
|
a925ecea72 | ||
|
|
3b53014d90 | ||
|
|
ea690e1c09 | ||
|
|
0c77366b23 | ||
|
|
5ecff02a63 | ||
|
|
545c43ec0f |
36
NOTICE.md
36
NOTICE.md
@@ -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.
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
jschuh@chromium.org
|
||||
tsepez@chromium.org
|
||||
|
||||
|
||||
# COMPONENT: Internals
|
||||
@@ -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.
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -1,13 +0,0 @@
|
||||
{"Registrations":[
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/chromium/chromium",
|
||||
"commitHash": "d8710dd959da8e3be56f20af8cc94fbf560fbb6b"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
||||
@@ -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.)
|
||||
|
||||

|
||||
|
||||
Then when you run something with `DebugBreak()` in it, you will see this:
|
||||

|
||||
|
||||
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*.
|
||||
@@ -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).
|
||||
|
||||
@@ -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 [-])"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 `Register‑ArgumentCompleter` 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
|
||||
@@ -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`:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)' >= '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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
//
|
||||
// pch.cpp
|
||||
// Include the standard header and generate the precompiled header.
|
||||
//
|
||||
|
||||
#include "pch.h"
|
||||
@@ -1,8 +0,0 @@
|
||||
//
|
||||
// pch.h
|
||||
// Header for standard system include files.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "UnitTestApp.xaml.h"
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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*/);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -27,5 +27,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
|
||||
void NotifyFocusEnter();
|
||||
void NotifyFocusLeave();
|
||||
|
||||
void Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#include <windows.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#include "windows.h"
|
||||
#include "wincon.h"
|
||||
#include "windowsx.h"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -307,7 +307,5 @@ private:
|
||||
friend class TextBufferIteratorTests;
|
||||
friend class ScreenBufferTests;
|
||||
friend class CommonState;
|
||||
friend class ConptyOutputTests;
|
||||
friend class ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -149,7 +149,7 @@ class TextBufferTests
|
||||
|
||||
void TextBufferTests::TestBufferCreate()
|
||||
{
|
||||
VERIFY_SUCCEEDED(m_state->GetTextBufferInfoInitResult());
|
||||
VERIFY_SUCCESS_NTSTATUS(m_state->GetTextBufferInfoInitResult());
|
||||
}
|
||||
|
||||
TextBuffer& TextBufferTests::GetTbi()
|
||||
|
||||
@@ -36,7 +36,6 @@ SOURCES = \
|
||||
InputBufferTests.cpp \
|
||||
VtIoTests.cpp \
|
||||
VtRendererTests.cpp \
|
||||
ConptyOutputTests.cpp \
|
||||
ViewportTests.cpp \
|
||||
ConsoleArgumentsTests.cpp \
|
||||
CommandLineTests.cpp \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#define DEFINE_CONSOLEV2_PROPERTIES
|
||||
#define INC_OLE2
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace Microsoft::Console::Render
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class VtRendererTest;
|
||||
friend class ConptyOutputTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user