Compare commits

...

50 Commits

Author SHA1 Message Date
Mike Griese
068a98c40e Some percent 0-100 of this is garbage
I'm not sure any of this is right. It all feels wrong. The problem we're
  having is that when conhost defers the EOL wrap, our cursor is still at the
  last cell of a row, and we still try and paint it there. That paintCursor,
  right before the end of the frame, is what's screwing us up

  I'm pretty sure the RenderData code to move the cursor ++ when we're at a
  delayed EOL is definitely wrong
2020-03-25 17:06:13 -05:00
Mike Griese
c0bb37c291 This looks like it might hypothetically work, lets check the tests... 2020-03-25 15:13:50 -05:00
Mike Griese
9d79c5c3fe Revert "This is complicated scrolling math and I hate it"
This reverts commit 112cb0ab8d.
2020-03-25 14:43:54 -05:00
Mike Griese
112cb0ab8d This is complicated scrolling math and I hate it 2020-03-25 14:43:34 -05:00
Mike Griese
d972b5e07a More tracing is always good 2020-03-25 14:41:36 -05:00
Dustin L. Howett (MSFT)
48480e6998 Figure out if the x64 hosted tools make our build better or worse (#4956)
This commit may help with the compiler and linker running out of memory.
2020-03-25 15:47:17 +00:00
Michael Niksa
680577f55c Update til::bitmap to use dynamic_bitset<> + libpopcnt (#5092)
This commit replaces `std::vector<bool>` with `dynamic_bitset<>` by
@pinam45 (https://github.com/pinam45/dynamic_bitset) and with
`libpopcnt` for high-performance bit counting by @kimwalisch
(https://github.com/kimwalisch/libpopcnt).

* [x] In support of performance, incremental rendering, and Terminal
  "not speed enough" as well as my sanity relative to
  `std::vector<bool>`
* [x] Tests updated and passed.
* [x] `LICENSE`, `NOTICE`, and provenance files updated.
* [x] I'm a core contributor. I discussed it with @DHowett-MSFT and
  cleared the licensing checks before pulling this in.

## Details `std::vector<bool>` provided by the Microsoft VC Runtime is
incapable of a great many things. Many of the methods you come to expect
off of `std::vector<T>` that are dutifully presented through the `bool`
variant will spontaneously fail at some future date because it decides
you allocated, resized, or manipulated the `vector<bool>` specialization
in an unsupported manner. Half of the methods will straight up not work
for filling/resizing in bulk. And you will tear your hair out as it will
somehow magically forget the assignment of half the bits you gave it
part way through an iteration then assert out and die.

As such, to preserve my sanity, I searched for an alternative. I came
across the self-contained header-only library `dynamic_bitset` by
@pinam45 which appears to do as much of `boost::dynamic_bitset` as I
wanted, but without including 400kg of boost libraries. It also has a
nifty optional dependency on `libpopcnt` by @kimwalisch that will use
processor-specific extensions for rapidly counting bits. @DHowett-MSFT
and I briefly discussed how nice `popcnt` would have been on
`std::vector<bool>` last week... and now we can have it. (To be fair, I
don't believe I'm using it yet... but we'll be able to easily dial in
`til::bitmap` soon and not worry about a performance hit if we do have
to walk bits and count them thanks to `libpopcnt`.)

This PR specifically focuses on swapping the dependencies out and
ingesting the new libraries. We'll further tune `til::bitmap` in future
pulls as necessary.

## Validation
* [x] Ran the automated tests for bitmap.
* [x] Ran the terminal manually and it looks fine still.
2020-03-25 02:41:10 +00:00
Leon Liang
1e30b53867 Fix broken Chinese IME when deleting composition (#5109)
## Summary of the Pull Request
This PR fixes an out of bounds access when deleting composition during Chinese IME. What's happening is that we're receiving a CompositionCompleted before receiving the TextUpdate to tell us to delete the last character in the composition. This creates two problems for us:
1. The final character gets sent to the Terminal when it should have been deleted.
2. `_activeTextStart` gets set to `_inputBuffer.length()` when sending the character to the terminal, so when the `TextUpdate` comes right after the `CompositionCompleted` event, `_activeTextStart` is out of sync.

This PR fixes the second issue, by updating `_activeTextStart` during a `TextUpdate` in case we run into this issue. 
The first issue is trickier to resolve since we assume that if the text server api tells us a composition is completed, we should send what we have. It'll be tracked here: #5110.
At the very least, this PR will let users continue to type in Chinese IME without it breaking, but it will still be annoying to see the first letter of your composition reappear after deleting it.

## PR Checklist
* [x] Closes #5054
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed

## Validation Steps Performed
Play around with Chinese IME deleting and composing, and play around with Korean and Japanese IME to see that it still works as expected.
2020-03-25 00:00:18 +00:00
David Windehem
c9ac0b7b85 Improve keybinding JSON schema (#3904)
The pattern regex now correctly disallows keybindings consisting of only
modifiers, modifiers not separated by "+", and unknown keys. Certain
shift+numpad combinations are also not allowed.

The description lists allowed key names in tabular format (assuming the
client renders \t correctly).
2020-03-24 15:06:23 -07:00
Dominik Bartsch
7250469dd5 doc: add cmder icon to ThirdPartyToolProfiles (#5107) 2020-03-24 14:14:12 -07:00
Dustin L. Howett (MSFT)
176badf36e Make the window border actually follow the user's theme (#5105)
## Summary of the Pull Request

One of our great contributors already hooked up all the logic for this,
we just needed a theme library that could handle the request.

## PR Checklist
* [x] Fixes #4980
* [x] CLA signed
* [x] Tests added/passed
* [ ] Requires documentation to be updated
2020-03-24 19:47:01 +00:00
Carlos Zamora
403069ccad Add enhanced key support for ConPty (#5021)
## Summary of the Pull Request
ConPty did not set the ENHANCED_KEY flag when generating new input. This change helps detect when it's supposed to do so, and sends it.

## References
[Enhanced Key Documentation](https://docs.microsoft.com/en-us/windows/console/key-event-record-str)

## PR Checklist
* [X] Closes #2397

## Detailed Description of the Pull Request / Additional comments
| KEY_EVENT_RECORD modifiers | VT encodable? | Detectable on the way out? |
|----------------------------|---------------|----------------------------|
| CAPSLOCK_ON                | No            | No                         |
| ENHANCED_KEY               | No            | Yes**                      |
| LEFT_ALT_PRESSED           | Yes*          | Yes*                       |
| LEFT_CTRL_PRESSED          | Yes*          | Yes*                       |
| NUMLOCK_ON                 | No            | No                         |
| RIGHT_ALT_PRESSED          | Yes*          | Yes*                       |
| RIGHT_CTRL_PRESSED         | Yes*          | Yes*                       |
| SCROLLLOCK_ON              | No            | No                         |
| SHIFT_PRESSED              | Yes           | Yes                        |
```
* We can detect Alt and Ctrl, but not necessarily which one
** Enhanced Keys are limited to the following:
    - off keypad: INS, DEL, HOME, END, PAGE UP, PAGE DOWN, direction keys
    - on keypad: / and ENTER
   Since we can't detect the keypad keys, those will _not_ send the ENHANCED_KEY modifier.
   For the following CSI action codes, we can assume that they are Enhanced Keys:
     case CsiActionCodes::ArrowUp:
     case CsiActionCodes::ArrowDown:
     case CsiActionCodes::ArrowRight:
     case CsiActionCodes::ArrowLeft:
     case CsiActionCodes::Home:
     case CsiActionCodes::End:
     case CsiActionCodes::CSI_F1:
     case CsiActionCodes::CSI_F3:
     case CsiActionCodes::CSI_F2:
     case CsiActionCodes::CSI_F4:
   These cases are handled in ActionCsiDispatch
```
## Validation Steps Performed
Followed bug repro steps. It now matches!
2020-03-24 19:45:56 +00:00
Carlos Zamora
a3382276d7 Improve wide glyph support in UIA (#4946)
## Summary of the Pull Request
- Added better wide glyph support for UIA. We used to move one _cell_ at a time, so wide glyphs would be read twice.
- Converted a few things to use til::point since I'm already here.
- fixed telemetry for UIA

## PR Checklist
* [x] Closes #1354

## Detailed Description of the Pull Request / Additional comments
The text buffer has a concept of word boundaries, so it makes sense to have a concept of glyph boundaries too.

_start and _end in UiaTextRange are now til::point

## Validation Steps Performed
Verified using Narrator
2020-03-23 23:50:17 +00:00
Mike Griese
e05507982d Make sure to account for the size the padding _will be_ scaled to (#5091)
* [x] Fixes #2061 for good this time
2020-03-23 22:24:33 +00:00
Dustin L. Howett (MSFT)
f088ae62b3 Fix the case sensitivity button in search on High Contrast (#5088)
Because the `Path` inside the case sensitivity button is not _text_,
it wasn't getting themed by the Xaml runtime's text style.
2020-03-23 11:24:27 -07:00
Christoph Kröppl
03f805cc0a doc: add MSYS2 profile to ThirdPartyToolProfiles.md (#5077) 2020-03-23 10:30:52 -07:00
Dustin L. Howett (MSFT)
69d99a7a2b deps: upgrade CppWinRT to 2.0.200316.3, gsl to v2.1.0 (#4536)
This commit upgrades C++/WinRT to 2.0.200316.3 and fixes a couple of the
transitive dependency issues we had in the process.

Because the latest version has better dependency resolution, we're able
to properly depend on Microsoft.UI.Xaml and the Toolkit in TerminalApp
and TerminalAppLib so we no longer need to manually include .dll and
.pri files.

Because of nebulous _other_ changes in dependency resolution,
WindowsTerminalUniversal isn't picking up transitive .winmd dependencies
from TerminalApp, and needs to include them as ProjectReferences
directly. This was already happening transitively, so now it's explicit.

I've also taken the time to upgrade GSL to v2.1.0, the last release
before they removed span::at and blew up our world.
2020-03-23 17:15:24 +00:00
Dustin L. Howett (MSFT)
2afa19fc15 version: bump to 0.11 2020-03-23 10:04:37 -07:00
pi1024e
27b28edcee Replace casting 0 to a pointer with nullptr (#5062)
## Summary of the Pull Request
When I did my last PR that was merged, the PR #4960, there were two more cases I forgot to include, so I included them here, for the sake of consistency and completion

## References
PR #4690

## PR Checklist
* [X] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [X] Tests added/passed

## Detailed Description of the Pull Request / Additional comments
Replacing pointer casts to 0 with nullptr in two tests.

## Validation Steps Performed
Manual Testing
Automated Testing
2020-03-23 09:38:39 -07:00
Michael Niksa
ca33d895a3 Move ConPTY to use til::bitmap (#5024)
## Summary of the Pull Request
Moves the ConPTY drawing mechanism (`VtRenderer`) to use the fine-grained `til::bitmap` individual-dirty-bit tracking mechanism instead of coarse-grained rectangle unions to improve drawing performance by dramatically reducing the total area redrawn.

## PR Checklist
* [x] Part of #778 and #1064 
* [x] I work here
* [x] Tests added and updated.
* [x] I'm a core contributor

## Detailed Description of the Pull Request / Additional comments
- Converted `GetDirtyArea()` interface from `IRenderEngine` to use a vector of `til::rectangle` instead of the `SMALL_RECT` to banhammer inclusive rectangles.
- `VtEngine` now holds and operates on the `til::bitmap` for invalidation regions. All invalidation operation functions that used to be embedded inside `VtEngine` are deleted in favor of using the ones in `til::bitmap`.
- Updated `VtEngine` tracing to use new `til::bitmap` on trace and the new `to_string()` methods detailed below.
- Comparison operators for `til::bitmap` and complementary tests.
- Fixed an issue where the dirty rectangle shortcut in `til::bitmap` was set to 0,0,0,0 by default which means that `|=` on it with each `set()` operation was stretching the rectangle from 0,0. Now it's a `std::optional` so it has no value after just being cleared and will build from whatever the first invalidated rectangle is. Complementary tests added.
- Optional run caching for `til::bitmap` in the `runs()` method since both VT and DX renderers will likely want to generate the set of runs at the beginning of a frame and refer to them over and over through that frame. Saves the iteration and creation and caches inside `til::bitmap` where the chance of invalidation of the underlying data is known best. It is still possible to iterate manually with `begin()` and `end()` from the outside without caching, if desired. Complementary tests added.
- WEX templates added for `til::bitmap` and used in tests.
- `translate()` method for `til::bitmap` which will slide the dirty points in the direction specified by a `til::point` and optionally back-fill the uncovered area as dirty. Complementary tests added.
- Moves all string generation for `til` types `size`, `point`, `rectangle`, and `some` into a `to_string` method on each object such that it can be used in both ETW tracing scenarios AND in the TAEF templates uniformly. Adds a similar method for `bitmap`.
- Add tagging to `_bitmap_const_iterator` such that it appears as a valid **Input Iterator** to STL collections and can be used in a `std::vector` constructor as a range. Adds and cleans up operators on this iterator to match the theoretical requirements for an **Input Iterator**. Complementary tests added.
- Add loose operators to `til` which will allow some basic math operations (+, -, *, /) between `til::size` and `til::point` and vice versa. Complementary tests added. Complementary tests added.
- Adds operators to `til::rectangle` to allow scaling with basic math operations (+, -, *) versus `til::size` and translation with basic math operations (+, -) against `til::point`. Complementary tests added.
- In-place variants of some operations added to assorted `til` objects. Complementary tests added.
- Update VT tests to compare invalidation against the new map structure instead of raw rectangles where possible.

## Validation Steps Performed
- Wrote additional til Unit Tests for all additional operators and functions added to the project to support this operation
- Updated the existing VT renderer tests
- Ran perf check
2020-03-23 15:57:54 +00:00
James Holderness
cb3bab4ea8 Fix the Alternate Scroll Mode when DECCKM enabled (#5081)
## Summary of the Pull Request

If the _Alternate Scroll Mode_ is enabled, the terminal generates up/down keystrokes when the mouse wheel is scrolled. However, the expected escape sequences for those keys are dependent on the state of the _Cursor Keys Mode_ ( `DECCKM`), but we haven't taken that into account. This PR updates the alternate scroll implementation to make sure the appropriate sequences are sent for both `DECCKM` modes.

## References

#3321

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

## Detailed Description of the Pull Request / Additional comments

I've simply added a condition in the `TerminalInput::_SendAlternateScroll` method to send a different pair of sequences dependent on the state of `_cursorApplicationMode`  flag.

## Validation Steps Performed

Manually tested in VIM (although that required me enabling the _Alternate Scroll Mode_ myself first). Also added a new unit test in `MouseInputTest` to confirm the correct sequences were generated for both `DECCKM` modes.
2020-03-23 13:00:59 +00:00
Mike Griese
0f82811363 Make sure to InvalidateAll on a resize (#5046)
## Summary of the Pull Request

Seriously just read the code on this one, it's so incredibly obvious what I did wrong

## References

Regressed with #4741 

## PR Checklist
* [x] Closes #5029
* [x] I work here
* [ ] Tests added/passed
* [n/a] Requires documentation to be updated
2020-03-20 23:50:59 +00:00
Mike Griese
99fa9460fd Gracefully handle json data with the wrong value types (#4961)
## Summary of the Pull Request

Currently, if the Terminal attempts to parse a setting that _should_ be a `bool`
and the user provided a string, then we'll throw an exception while parsing the
settings, and display an error message that's pretty unrelated to the actual
problem.

The same goes for `bool`s as `int`s, `float`s as `int`s, etc.

This PR instead updates our settings parsing to ensure that we check the type of
a json value before actually trying to get its parsed value.

## References

## PR Checklist
* [x] Closes #4299
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

I made a bunch of `JsonUtils` helpers for this in the same vein as the
`GetOptionalValue` ones.

Notably, any other value type can safely be treated as a string value.

## Validation Steps Performed
* added tests
* ran the Terminal and verified we can parse settings with the wrong types
2020-03-20 20:35:51 +00:00
pi1024e
9f95b54f2c Change NULL to nullptr since they are pointers (#4960)
Some functions and variables are having NULL assigned to them when they are in fact pointers, so nullptr might be more accurate here.
2020-03-20 20:35:12 +00:00
Dustin L. Howett (MSFT)
1e57dde30b Set the ComponentResourceLocation for our DLL projects (#5031)
These projects load Xaml components that have Uids, but those Uids just
weren't working because Xaml components are, by default, loaded in
"Application" scope. Application scope is great if the resource producer
is the EXE project.

Application scope means that resources are looked up at the resource
root, but DLLs with resources don't produce resources at the root. They
produce resources at a root named after the DLL.

Setting the Xaml component resource location to Nested makes sure the
Xaml resource loader loads resources from the right places.
2020-03-20 13:08:32 -07:00
Leon Liang
5672636568 Allow IME Text Wrapping (#5005)
## Summary of the Pull Request
This PR turns on TextWrapping on `TSFInputControl::TextBlock`. Once the TextBlock hits the end of the Terminal window, it will wrap downwards, but the TextBlock will have as much width as it had when composition started. Unfortunately, this means if composition starts right at the end of the Terminal with enough width for just one character, there will be a vertical line of characters down the right side of the Terminal 😅. It's definitely not ideal, and I imagine this won't be the last time we visit this issue, but for now users can see what they're typing.

## PR Checklist
* [x] Closes #3657
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed

## Validation Steps Performed
Played around with IME towards the edge of the Terminal window.
2020-03-19 19:15:13 +00:00
Dustin Howett
a68fa47e52 Merge branch 'inbox' into master 2020-03-19 11:17:08 -07:00
Dustin Howett
2f203ff1b3 Invoke-CodeFormat on inbox changes 2020-03-19 11:16:32 -07:00
Michael Niksa
f141d86280 Merged PR 4447792: Fix two bugs with DOSKEY
The first issue is in the console host: when we erase a command history,
we also clear its _allocated_ flag. It's supposed to remain allocated
but become "reset". When we later check that a command history that
exists in the list is allocated, we fail loudly because allocated has
been cleared.

The second is that in Windows Server 2003, we rewrote the console client
APIs (in kernelbase!) regarding command history and changed one internal
function from taking char** to taking char*. Since the signature was
_actually_ void** and that changed to void*, the compiler didn't notice
when in only one single place we continued to pass a char** instead of a
char*.  This caused us to send the wrong filename length for the ExeName
in SetConsoleNumberOfCommands.

Fixes MSFT:25265854
Retrieved from https://microsoft.visualstudio.com os OS official/rs_onecore_dep_uxp b493fb5a06975c53b2fbb7b9fc0546244b551fa9
2020-03-19 18:14:52 +00:00
Marcel Wagner
e4bb63ce47 doc: add docs for updating nuget packages and using .nupkgs (#4996)
This adds documentation regarding updating nuget versions and using .nupkg files instead of downloading them from Nuget repository.
2020-03-19 10:14:50 -07:00
Carlos Zamora
ae3f8f3759 Add explicit to bool operators of Point and Rect (#4948)
Found a bug where the following won't work:
```c++
COORD inclusiveEnd{ _end };
```
where `_end` is a `til::point`.

The only fix for this is to replace these instances with this:
```c++
COORD inclusiveEnd = _end;
```

What was happening in the first notation is the implicit conversion of `til::point` to `bool` to `SHORT`. The constructor for COORD only sees one SHORT so it thinks the value should be the definition for X, and Y should stay as 0. So we end up getting `1, 0`.

By adding the explicit keyword to the bool operators, we prevent the accident above from occurring.
2020-03-19 16:12:15 +00:00
Michael Niksa
9e9473cfb2 til::bitmap (#4967)
## Summary of the Pull Request
Introduces type `til::bitmap` which implements an NxM grid of bits that can be used to track dirty/clean state on a per-cell basis throughout a rectangle.

## PR Checklist
* [x] In support of Differential Rendering #778
* [X] I work here.
* [x] Tests added/passed
* [x] I'm a core contributor.

## Detailed Description of the Pull Request / Additional comments
- Adds `const_iterator` to `til::rectangle` that will walk from top to bottom, left to right every position in the rectangle as a `til::point` and associated test.
- Adds `bool til::rectangle::contains(til::point)` to determine if a point lies within the rectangle and the associated test
- Adds complementary methods to `til::rectangle` of `index_of(til::point)` and `point_at(ptrdiff_t)` which will convert between a valid `point` position that lies inside the `rectangle` and the index as a count of cells from the top left corner (origin) in a top to bottom & left to right counting fashion (and associated tests).
- Adds `til::some<T, N>::clear()` to empty out the contents of the `some` and associated test.
THEN with all that support...
- Adds `til::bitmap` which represents a 2 dimensional grid of boolean/bit flags. This class contains set and reset methods for the entire region, and set only for a single `til::point` or a subregion as specified by a `til::rectangle` (and associated tests.) 
- Adds convenience methods of `any()`, `one()`, `none()`, and `all()` to the `til::bitmap` to check some of its state.
- Adds convenience method of `resize()` to `til::bitmap` that will grow or shrink the bitmap, copying whatever is left of the previous one that still fits and optionally filling or blanking the new space.
- Adds a `const_iterator` for `til::bitmap` that will walk top to bottom, left to right and return a `til::rectangle` representing a run of bits that are all on sequentially in a row. Breaks per row. Exactly as we expect to be drawing things (and associated tests.)

## Validation Steps Performed
- See automated tests of functionality.
2020-03-19 16:10:13 +00:00
pi1024e
ddcdff15d3 hygiene: change default specifiers of some functions to delete (#4962)
For some functions, the overriding implementation is set to default, but
the deletion is not explicitly set at all. For those functions, I
changed default to delete
2020-03-18 17:30:50 -07:00
Dustin L. Howett (MSFT)
aaa4943112 Pin the DisplayName to Windows Terminal to fix the UAC prompt (#4995)
There's an issue in the UAC consent dialog where it cannot read an
application's name if it's stored in a resource. When it fails, it deems
us an "Unknown Program" and that looks pretty silly.

Fixes #2289.
2020-03-18 23:19:06 +00:00
Mike Griese
f7d106d3f3 Add support for Ctrl+# keys (#4938)
## Summary of the Pull Request

Fixes the <kbd>Ctrl+Num</kbd> keys in both conhost and the Terminal. These keys are supposed to be mapped to specific characters according to [this doc](https://vt100.net/docs/vt220-rm/table3-5.html). Now we actually handle them correctly.

## PR Checklist
* [x] Closes #3507 
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed

* Ran test
* tested in `gnome-terminal` with `showkeys -a`
* tested in conhost with `showkeys -a`
* tested in Windows Terminal with `showkeys -a`
2020-03-18 22:39:22 +00:00
Mike Griese
d50409b901 Fix VT sequences for Ctrl+Alt+? input (#4947)
## Summary of the Pull Request

Ctrl+/ and Ctrl-? are complicated in VT input.

* C-/ is supposed to be `^_` (the C0 character US)
* C-? is supposed to be `DEL`
* C-M-/ is supposed to be `^[^_` (ESC US)
* C-M-? is supposed to be `^[^?` (ESC DEL)

The astute reader will note that these characters also share the same key on a
standard en-us keyboard layout, which makes the problem even more complicated.
This PR does a better job of handling these weird cases.

# References
* #3079 - At first, I thought this PR would close this issue, but as I've learned below, this won't solve that one. This bug was merely found during that investigation.

## PR Checklist
* [x] Related to #3079
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed

* ran tests
* checked `showkey -a` in gnome-terminal, which gives you the wrong results for C-M-/, C-M-?
* checked `showkey -a` in xterm, which gives you the _right_ results for C-M-/, C-M-?
* checked `showkey -a` in conhost
* checked `showkey -a` in Windows Terminal
2020-03-18 22:38:31 +00:00
Mike Griese
f221cd245e Clamp the terminal buffer to SHRT_MAX on resize (#4964)
## Summary of the Pull Request

This is 100% on me. Even after mucking around in this function for the last 3
months, I missed that there was a single addition where we weren't doing a
clamped addition. This would lead to us creating a buffer with negative height,
and all sorts of badness.

Clamping this addition was enough to fix the bug.

## PR Checklist
* [x] Closes #2815
* [x] Closes #4972
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed
* ran tests
* Created a profile with `"historySize" : 32728`, then filled the viewport with
  text, then maximized, and saw that the viewport indeed did resize to the new
  size of the window.
2020-03-18 22:22:26 +00:00
Carlos Zamora
862793299a Properly represent block selections in UIA (#4991)
## Summary of the Pull Request
Block selections were always read and displayed as line selections in UIA. This fixes that.

## PR Checklist
* [x] Closes #4509 

## Detailed Description of the Pull Request / Additional comments
1. Expose `IsBlockSelection()` via IUiaData
2. Update the constructor to be able to take in a block selection parameter
3. Make ScreenInfoUiaProviders pass step 1 output into step 2 constructor
4. Update all instances of `UiaTextRange::GetTextRects()` to include this new flag

## Validation Steps Performed
Manually tested.
Additional tests would be redundant as GetTextRects() is tested in the text buffer.
2020-03-18 21:03:51 +00:00
Dustin L. Howett (MSFT)
d392a48857 Fix an off-by-one that made us use EL instead of ECH (#4994)
When we painted spaces up until the character right before the right
edge of the screen, we would erroneously use Erase in Line instead of
Erase Character due to an off-by-one.

Fixes #4727
2020-03-18 13:24:20 -07:00
Carlos Zamora
eca0c6b327 Fire UIA Events for Output and Cursor Movement (#4826)
## Summary of the Pull Request
This notifies automation clients (i.e.: NVDA, narrator, etc...) of new output being rendered to the screen.

## References
Close #2447 - Signaling for new output and cursor
Close #3791 - fixed by signaling cursor changes

## Detailed Description of the Pull Request / Additional comments
- Added tracing for UiaRenderer. This makes it easier to debug issues with notifying an automation client.
- Fire TextChanged automation events when new content is output to the screen.

## Validation Steps Performed
Verified with NVDA [1]

## Narrator
Narrator works _better_, but is unable to detect new output consistently. There is no harm for narrator when this change goes in.

[1] https://github.com/microsoft/terminal/issues/2447#issuecomment-595879890
2020-03-18 18:01:05 +00:00
Leon Liang
cddac25726 Ignore KeyDown events during Alt-Numpad Input (#4965)
## Summary of the Pull Request
Alt-Numpad# input would be escaping each numkey before sending it through. This would result in some weird behavior, for example, in powershell, where the first alt-numpad# would start digit argument and once the user releases alt, a character is sent through and digit argument would repeat that character X times. To resolve this, we simply need to ignore KeyDowns where Alt is held and a Numpad# is pressed. 

Once Alt is released, we'll receive a character through `TSFInputControl`, not `TermControl::CharacterHandler`. It seems that the `CoreTextEditContext` in `TSFInputControl` intercepts the character before it gets to `TermControl`. TSF will then send the received character through as normal.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #1401 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Testing various combinations of Alt-Numpad# consistently sends through only one instance of the expected symbols.
2020-03-18 17:58:10 +00:00
Mike Griese
8211ed9fa6 Fix unbinding keys in v0.10 (#4988)
## Summary of the Pull Request

We (the royal "we") broke key unbinding in #4746. We didn't run the local tests after this, which actually would have caught this. The comment even suggests what we should have done here. We need to make sure that when we bail, it's because there's a parsing function that returned nothing. `null`, `"unbound"`, etc actually don't even have a parsing function at all, so they should just keep on keepin' on.

## References

Source of this regression: #4746

## PR Checklist
* [x] Closes #3729
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

This is a great example of why your unittests should run in CI always

## Validation Steps Performed
* **ran the tests**
* tested the following unbindings:

```json
        { "command": null, "keys": [ "ctrl+shift+t" ] },
        { "command": "unbound", "keys": [ "ctrl+shift+t" ] },
        { "command": "null", "keys": [ "ctrl+shift+t" ] },

```
and they each individually worked.
2020-03-18 12:52:43 -05:00
Yitzhak Steinmetz
c0d704e734 Add cursorColor to color scheme (#4651)
Add the option to set the cursor color as part of the color scheme.

This is very useful for light themes, where the cursor disappears unless its color
is set in the profile.

Related to issue #764, but doesn't fully resolve it.

## Validation
I tested this manually by creating a light color scheme, setting the cursor color
to black and setting the profile color scheme to the newly created color scheme.
I validated the cursor is black, then set the cursor color in the profile (to red)
and saw it trumps the cursor color from the color scheme.
2020-03-17 20:11:03 +00:00
pi1024e
7621994b46 Make loop in IInputEvent more consistent and modern (#4959)
In IInputEvent, there are two for loops. One is a range based loop operating on "records", and the other is a classic for-loop doing the same. For consistency, the for-loop was changed into the more modern variation via a compiler refactoring, which has the exact same behavior as the other for-loop in the main function.

Yes, of course this was tested manually and with the unit tests.

# Validation Steps
Unit testing passed. In addition, for the manual validation tests, I compared the output for sample values between the two loops ensuring the same results.
2020-03-17 10:52:33 -07:00
Carlos Zamora
1d8c5bae35 Make panes use xaml star sizing (#4953)
## Summary of the Pull Request
Dustin and Mike had a discussion on star sizing. Mcpiroman connected the dots. I just swooped in and implemented it.

## References
https://github.com/microsoft/terminal/issues/3996#issuecomment-566676111
https://github.com/microsoft/terminal/issues/3744#issuecomment-568926636

## PR Checklist
* [X] Closes #3744

## Detailed Description of the Pull Request / Additional comments
Star sizing allows us to keep things proportional. Since a 1::1 proportion is the same as a 100::100 proportion, I just went in and added star sizing. In the words of a some dude with a big metal fist, everything is perfectly balanced now.

![image](https://user-images.githubusercontent.com/11050425/76813679-f7103f00-67b5-11ea-9b5c-d2cc73673aba.png)



## Validation Steps Performed
Verified for vertical, horizontal, and uneven splits.
2020-03-17 17:44:45 +00:00
James Holderness
8a9475aeb2 Update comments in the ITermDispatch interface to better indicate the VT commands that are supported. (#4752)
## Summary of the Pull Request

Most of the methods in the `ITermDispatch` interface have a comment following them that indicates the VT function that they implement. These comments are then used by the script in PR #1884 to generate a table of supported VT functions. This PR updates some of those comments, to more accurately reflect the functions that are actually supported.

## References

PR #1884 

## PR Checklist
* [ ] Closes #xxx
* [x] CLA signed.
* [x] No new tests.
* [x] No new docs.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #1884

## Detailed Description of the Pull Request / Additional comments

In some cases there are methods that implement multiple VT functions which are essentially aliases. Originally the comments listed only one of the functions, so I've now updated them to list both. This includes `HPA` as an alias of `CHA`, and `HVP` as an alias of `CUP`.

Similarly, some control characters are implemented in terms of another VT function, but only the main function was listed in the comment. Again I've now updated the comments to list both the main function and any related control characters. This includes `BS` (sharing the same method as `CUB`), `HT` (the same method as `CHT`), and `LF`, `FF`, and `VT` (the same method as `IND` and `NEL`).

Then there were some minor corrections. The `DeviceAttributes` method was commented as `DA`, but it really should be `DA1`. `DesignateCharset` was simply commented as _DesignateCharset_, when it should be `SCS`. The `DECSCNM` comment was missing a space, so it wasn't picked up by the script. And the `SetColumns` comment mistakenly included `DECSCPP`, but we don't actually support that.

Finally there is the `DeviceStatusReport` method, which potentially covers a wide range of different reports. But for now we only support the _Cursor Position Report_, so I've commented it as `DSR, DSR-CPR` to more clearly indicate our level of support. In the long term we'll probably need a better way of handling these reports though.

## Validation Steps Performed

I've run the script from PR #1884 and confirmed that the output is now a more accurate reflection of our actual VT support.
2020-03-17 15:55:22 +00:00
Michael Niksa
ff1337ddb0 Import build fix changes from OS for sync to a34a957cf
Retrieved from https://microsoft.visualstudio.com os OS official/rs_onecore_dep_uxp 5b3acd8b5bac38da02fc86a29c81dfd252e79d1f

Related work items: MSFT:25505535
2020-03-16 18:26:48 +00:00
Leon Liang
57c7d1d7ae Force WslDistroGenerator to timeout after 10s and return Profiles (#4905)
When WSL.exe would hang for users, WslDistroGenerator would also hang
while waiting for its `wsl.exe --list` call to return. The timeout was
`INFINITE` in the `WaitForSingleObject` call, but we should slap a
timeout on it instead (here we choose 2 seconds). In addition, if it
times out, we should also just return profiles and let the Terminal
continue to start up without the WSL distro profiles loaded.

# Validation Steps Performed
Made a sleep 30 executable as the command instead, made sure it hit the
`WAIT_TIMEOUT` and continued to start up without loading my Ubuntu
profile.

Closes #3987
2020-03-16 18:14:25 +00:00
Mike Griese
7b9c8c7055 Fix C-M-z, C-M-x in Conpty (#4940)
## Summary of the Pull Request

This PR ensures that Conpty properly treats `^[^Z` and `^[^X` as
<kbd>Ctrl+Alt+z</kbd> and <kbd>Ctrl+Alt+x</kbd>, instead of <kbd>Ctrl+z</kbd>
and <kbd>Ctrl+x</kbd>.

## References

## PR Checklist
* [x] Closes #4201
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

`^Z` and `^X` are special control characters, SUB and CAN. For the output state
machine, these characters are supposed to be executed from _any_ state. However,
we shouldn't do this for the input engine. With the current behavior, these
characters are immediately executed regardless of what state we're in. That
means we end up synthesizing <kbd>Ctrl+z/x</kbd> for these characters. However,
for the InputStateMachine engine, when these characters are preceeded by `^[`
(ESC), we want to treat them as <kbd>Ctrl+Alt+z/x</kbd>.

This just adds a check in `StateMachine` to see if we should immediately execute
these characters from any state, similar to many of the other exceptions we
already perform in the StateMachine for the input engine.

## Validation Steps Performed
* ran tests
* checked `showkey -a` in gnome-terminal
* checked `showkey -a` in conhost
* checked `showkey -a` in vt pipeterm (conhost as a conpty terminal)
* checked `showkey -a` in Windows Terminal
2020-03-16 16:59:48 +00:00
Carlos Zamora
860affd608 Make commands in doc appear as code (#4933)
Co-authored-by: Carlos Zamora <cazamor@microsoft.com>
2020-03-16 09:44:53 -07:00
203 changed files with 7655 additions and 1131 deletions

View File

@@ -80,7 +80,7 @@ SOFTWARE.
## chromium/base/numerics
**Source**:
**Source**: https://github.com/chromium/chromium/tree/master/base/numerics
### License
@@ -112,4 +112,71 @@ 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.
```
## kimwalisch/libpopcnt
**Source**: https://github.com/kimwalisch/libpopcnt
### License
```
BSD 2-Clause License
Copyright (c) 2016 - 2019, Kim Walisch
Copyright (c) 2016 - 2019, Wojciech Muła
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
```
## dynamic_bitset
**Source**: https://github.com/pinam45/dynamic_bitset
### License
```
MIT License
Copyright (c) 2019 Maxime Pinard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

View File

@@ -5,7 +5,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
<VersionMajor>0</VersionMajor>
<VersionMinor>10</VersionMinor>
<VersionMinor>11</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Maxime Pinard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,17 @@
### Notes for Future Maintainers
This was originally imported by @miniksa in March 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 pinam45/dynamic_bitset repository on GitHub.
2. Take the entire contents of the include directory wholesale and drop it in the root directory here.
3. Don't change anything about it.
4. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
5. Submit the pull.

View File

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

File diff suppressed because it is too large Load Diff

Submodule dep/gsl updated: 1212beae77...7e99e76c97

26
dep/libpopcnt/LICENSE Normal file
View File

@@ -0,0 +1,26 @@
BSD 2-Clause License
Copyright (c) 2016 - 2019, Kim Walisch
Copyright (c) 2016 - 2019, Wojciech Muła
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,17 @@
### Notes for Future Maintainers
This was originally imported by @miniksa in March 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 kimwalisch/libpopcnt repository on GitHub.
2. Take the `libpopcnt.h` file.
3. Don't change anything about it.
4. Validate that the `LICENSE` in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
5. Submit the pull.

View File

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

841
dep/libpopcnt/libpopcnt.h Normal file
View File

@@ -0,0 +1,841 @@
/*
* libpopcnt.h - C/C++ library for counting the number of 1 bits (bit
* population count) in an array as quickly as possible using
* specialized CPU instructions i.e. POPCNT, AVX2, AVX512, NEON.
*
* Copyright (c) 2016 - 2019, Kim Walisch
* Copyright (c) 2016 - 2018, Wojciech Muła
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. 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.
*
* 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.
*/
#ifndef LIBPOPCNT_H
#define LIBPOPCNT_H
#include <stdint.h>
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#ifdef __GNUC__
#define GNUC_PREREQ(x, y) \
(__GNUC__ > x || (__GNUC__ == x && __GNUC_MINOR__ >= y))
#else
#define GNUC_PREREQ(x, y) 0
#endif
#ifdef __clang__
#define CLANG_PREREQ(x, y) \
(__clang_major__ > x || (__clang_major__ == x && __clang_minor__ >= y))
#else
#define CLANG_PREREQ(x, y) 0
#endif
#if (_MSC_VER < 1900) && \
!defined(__cplusplus)
#define inline __inline
#endif
#if (defined(__i386__) || \
defined(__x86_64__) || \
defined(_M_IX86) || \
defined(_M_X64))
#define X86_OR_X64
#endif
#if defined(X86_OR_X64) && \
(defined(__cplusplus) || \
defined(_MSC_VER) || \
(GNUC_PREREQ(4, 2) || \
__has_builtin(__sync_val_compare_and_swap)))
#define HAVE_CPUID
#endif
#if GNUC_PREREQ(4, 2) || \
__has_builtin(__builtin_popcount)
#define HAVE_BUILTIN_POPCOUNT
#endif
#if GNUC_PREREQ(4, 2) || \
CLANG_PREREQ(3, 0)
#define HAVE_ASM_POPCNT
#endif
#if defined(HAVE_CPUID) && \
(defined(HAVE_ASM_POPCNT) || \
defined(_MSC_VER))
#define HAVE_POPCNT
#endif
#if defined(HAVE_CPUID) && \
GNUC_PREREQ(4, 9)
#define HAVE_AVX2
#endif
#if defined(HAVE_CPUID) && \
GNUC_PREREQ(5, 0)
#define HAVE_AVX512
#endif
#if defined(HAVE_CPUID) && \
defined(_MSC_VER) && \
defined(__AVX2__)
#define HAVE_AVX2
#endif
#if defined(HAVE_CPUID) && \
defined(_MSC_VER) && \
defined(__AVX512__)
#define HAVE_AVX512
#endif
#if defined(HAVE_CPUID) && \
CLANG_PREREQ(3, 8) && \
__has_attribute(target) && \
(!defined(_MSC_VER) || defined(__AVX2__)) && \
(!defined(__apple_build_version__) || __apple_build_version__ >= 8000000)
#define HAVE_AVX2
#define HAVE_AVX512
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*
* This uses fewer arithmetic operations than any other known
* implementation on machines with fast multiplication.
* It uses 12 arithmetic operations, one of which is a multiply.
* http://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation
*/
static inline uint64_t popcount64(uint64_t x)
{
uint64_t m1 = 0x5555555555555555ll;
uint64_t m2 = 0x3333333333333333ll;
uint64_t m4 = 0x0F0F0F0F0F0F0F0Fll;
uint64_t h01 = 0x0101010101010101ll;
x -= (x >> 1) & m1;
x = (x & m2) + ((x >> 2) & m2);
x = (x + (x >> 4)) & m4;
return (x * h01) >> 56;
}
#if defined(HAVE_ASM_POPCNT) && \
defined(__x86_64__)
static inline uint64_t popcnt64(uint64_t x)
{
__asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x));
return x;
}
#elif defined(HAVE_ASM_POPCNT) && \
defined(__i386__)
static inline uint32_t popcnt32(uint32_t x)
{
__asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x));
return x;
}
static inline uint64_t popcnt64(uint64_t x)
{
return popcnt32((uint32_t) x) +
popcnt32((uint32_t)(x >> 32));
}
#elif defined(_MSC_VER) && \
defined(_M_X64)
#include <nmmintrin.h>
static inline uint64_t popcnt64(uint64_t x)
{
return _mm_popcnt_u64(x);
}
#elif defined(_MSC_VER) && \
defined(_M_IX86)
#include <nmmintrin.h>
static inline uint64_t popcnt64(uint64_t x)
{
return _mm_popcnt_u32((uint32_t) x) +
_mm_popcnt_u32((uint32_t)(x >> 32));
}
/* non x86 CPUs */
#elif defined(HAVE_BUILTIN_POPCOUNT)
static inline uint64_t popcnt64(uint64_t x)
{
return __builtin_popcountll(x);
}
/* no hardware POPCNT,
* use pure integer algorithm */
#else
static inline uint64_t popcnt64(uint64_t x)
{
return popcount64(x);
}
#endif
static inline uint64_t popcnt64_unrolled(const uint64_t* data, uint64_t size)
{
uint64_t i = 0;
uint64_t limit = size - size % 4;
uint64_t cnt = 0;
for (; i < limit; i += 4)
{
cnt += popcnt64(data[i+0]);
cnt += popcnt64(data[i+1]);
cnt += popcnt64(data[i+2]);
cnt += popcnt64(data[i+3]);
}
for (; i < size; i++)
cnt += popcnt64(data[i]);
return cnt;
}
#if defined(HAVE_CPUID)
#if defined(_MSC_VER)
#include <intrin.h>
#include <immintrin.h>
#endif
/* %ecx bit flags */
#define bit_POPCNT (1 << 23)
/* %ebx bit flags */
#define bit_AVX2 (1 << 5)
#define bit_AVX512 (1 << 30)
/* xgetbv bit flags */
#define XSTATE_SSE (1 << 1)
#define XSTATE_YMM (1 << 2)
#define XSTATE_ZMM (7 << 5)
static inline void run_cpuid(int eax, int ecx, int* abcd)
{
#if defined(_MSC_VER)
__cpuidex(abcd, eax, ecx);
#else
int ebx = 0;
int edx = 0;
#if defined(__i386__) && \
defined(__PIC__)
/* in case of PIC under 32-bit EBX cannot be clobbered */
__asm__ ("movl %%ebx, %%edi;"
"cpuid;"
"xchgl %%ebx, %%edi;"
: "=D" (ebx),
"+a" (eax),
"+c" (ecx),
"=d" (edx));
#else
__asm__ ("cpuid;"
: "+b" (ebx),
"+a" (eax),
"+c" (ecx),
"=d" (edx));
#endif
abcd[0] = eax;
abcd[1] = ebx;
abcd[2] = ecx;
abcd[3] = edx;
#endif
}
#if defined(HAVE_AVX2) || \
defined(HAVE_AVX512)
static inline int get_xcr0()
{
int xcr0;
#if defined(_MSC_VER)
xcr0 = (int) _xgetbv(0);
#else
__asm__ ("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx" );
#endif
return xcr0;
}
#endif
static inline int get_cpuid()
{
int flags = 0;
int abcd[4];
run_cpuid(1, 0, abcd);
if ((abcd[2] & bit_POPCNT) == bit_POPCNT)
flags |= bit_POPCNT;
#if defined(HAVE_AVX2) || \
defined(HAVE_AVX512)
int osxsave_mask = (1 << 27);
/* ensure OS supports extended processor state management */
if ((abcd[2] & osxsave_mask) != osxsave_mask)
return 0;
int ymm_mask = XSTATE_SSE | XSTATE_YMM;
int zmm_mask = XSTATE_SSE | XSTATE_YMM | XSTATE_ZMM;
int xcr0 = get_xcr0();
if ((xcr0 & ymm_mask) == ymm_mask)
{
run_cpuid(7, 0, abcd);
if ((abcd[1] & bit_AVX2) == bit_AVX2)
flags |= bit_AVX2;
if ((xcr0 & zmm_mask) == zmm_mask)
{
if ((abcd[1] & bit_AVX512) == bit_AVX512)
flags |= bit_AVX512;
}
}
#endif
return flags;
}
#endif /* cpuid */
#if defined(HAVE_AVX2)
#include <immintrin.h>
#if !defined(_MSC_VER)
__attribute__ ((target ("avx2")))
#endif
static inline void CSA256(__m256i* h, __m256i* l, __m256i a, __m256i b, __m256i c)
{
__m256i u = _mm256_xor_si256(a, b);
*h = _mm256_or_si256(_mm256_and_si256(a, b), _mm256_and_si256(u, c));
*l = _mm256_xor_si256(u, c);
}
#if !defined(_MSC_VER)
__attribute__ ((target ("avx2")))
#endif
static inline __m256i popcnt256(__m256i v)
{
__m256i lookup1 = _mm256_setr_epi8(
4, 5, 5, 6, 5, 6, 6, 7,
5, 6, 6, 7, 6, 7, 7, 8,
4, 5, 5, 6, 5, 6, 6, 7,
5, 6, 6, 7, 6, 7, 7, 8
);
__m256i lookup2 = _mm256_setr_epi8(
4, 3, 3, 2, 3, 2, 2, 1,
3, 2, 2, 1, 2, 1, 1, 0,
4, 3, 3, 2, 3, 2, 2, 1,
3, 2, 2, 1, 2, 1, 1, 0
);
__m256i low_mask = _mm256_set1_epi8(0x0f);
__m256i lo = _mm256_and_si256(v, low_mask);
__m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), low_mask);
__m256i popcnt1 = _mm256_shuffle_epi8(lookup1, lo);
__m256i popcnt2 = _mm256_shuffle_epi8(lookup2, hi);
return _mm256_sad_epu8(popcnt1, popcnt2);
}
/*
* AVX2 Harley-Seal popcount (4th iteration).
* The algorithm is based on the paper "Faster Population Counts
* using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and
* Wojciech Mula (23 Nov 2016).
* @see https://arxiv.org/abs/1611.07612
*/
#if !defined(_MSC_VER)
__attribute__ ((target ("avx2")))
#endif
static inline uint64_t popcnt_avx2(const __m256i* data, uint64_t size)
{
__m256i cnt = _mm256_setzero_si256();
__m256i ones = _mm256_setzero_si256();
__m256i twos = _mm256_setzero_si256();
__m256i fours = _mm256_setzero_si256();
__m256i eights = _mm256_setzero_si256();
__m256i sixteens = _mm256_setzero_si256();
__m256i twosA, twosB, foursA, foursB, eightsA, eightsB;
uint64_t i = 0;
uint64_t limit = size - size % 16;
uint64_t* cnt64;
for(; i < limit; i += 16)
{
CSA256(&twosA, &ones, ones, data[i+0], data[i+1]);
CSA256(&twosB, &ones, ones, data[i+2], data[i+3]);
CSA256(&foursA, &twos, twos, twosA, twosB);
CSA256(&twosA, &ones, ones, data[i+4], data[i+5]);
CSA256(&twosB, &ones, ones, data[i+6], data[i+7]);
CSA256(&foursB, &twos, twos, twosA, twosB);
CSA256(&eightsA, &fours, fours, foursA, foursB);
CSA256(&twosA, &ones, ones, data[i+8], data[i+9]);
CSA256(&twosB, &ones, ones, data[i+10], data[i+11]);
CSA256(&foursA, &twos, twos, twosA, twosB);
CSA256(&twosA, &ones, ones, data[i+12], data[i+13]);
CSA256(&twosB, &ones, ones, data[i+14], data[i+15]);
CSA256(&foursB, &twos, twos, twosA, twosB);
CSA256(&eightsB, &fours, fours, foursA, foursB);
CSA256(&sixteens, &eights, eights, eightsA, eightsB);
cnt = _mm256_add_epi64(cnt, popcnt256(sixteens));
}
cnt = _mm256_slli_epi64(cnt, 4);
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(eights), 3));
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(fours), 2));
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(twos), 1));
cnt = _mm256_add_epi64(cnt, popcnt256(ones));
for(; i < size; i++)
cnt = _mm256_add_epi64(cnt, popcnt256(data[i]));
cnt64 = (uint64_t*) &cnt;
return cnt64[0] +
cnt64[1] +
cnt64[2] +
cnt64[3];
}
/* Align memory to 32 bytes boundary */
static inline void align_avx2(const uint8_t** p, uint64_t* size, uint64_t* cnt)
{
for (; (uintptr_t) *p % 8; (*p)++)
{
*cnt += popcnt64(**p);
*size -= 1;
}
for (; (uintptr_t) *p % 32; (*p) += 8)
{
*cnt += popcnt64(
*(const uint64_t*) *p);
*size -= 8;
}
}
#endif
#if defined(HAVE_AVX512)
#include <immintrin.h>
#if !defined(_MSC_VER)
__attribute__ ((target ("avx512bw")))
#endif
static inline __m512i popcnt512(__m512i v)
{
__m512i m1 = _mm512_set1_epi8(0x55);
__m512i m2 = _mm512_set1_epi8(0x33);
__m512i m4 = _mm512_set1_epi8(0x0F);
__m512i t1 = _mm512_sub_epi8(v, (_mm512_srli_epi16(v, 1) & m1));
__m512i t2 = _mm512_add_epi8(t1 & m2, (_mm512_srli_epi16(t1, 2) & m2));
__m512i t3 = _mm512_add_epi8(t2, _mm512_srli_epi16(t2, 4)) & m4;
return _mm512_sad_epu8(t3, _mm512_setzero_si512());
}
#if !defined(_MSC_VER)
__attribute__ ((target ("avx512bw")))
#endif
static inline void CSA512(__m512i* h, __m512i* l, __m512i a, __m512i b, __m512i c)
{
*l = _mm512_ternarylogic_epi32(c, b, a, 0x96);
*h = _mm512_ternarylogic_epi32(c, b, a, 0xe8);
}
/*
* AVX512 Harley-Seal popcount (4th iteration).
* The algorithm is based on the paper "Faster Population Counts
* using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and
* Wojciech Mula (23 Nov 2016).
* @see https://arxiv.org/abs/1611.07612
*/
#if !defined(_MSC_VER)
__attribute__ ((target ("avx512bw")))
#endif
static inline uint64_t popcnt_avx512(const __m512i* data, const uint64_t size)
{
__m512i cnt = _mm512_setzero_si512();
__m512i ones = _mm512_setzero_si512();
__m512i twos = _mm512_setzero_si512();
__m512i fours = _mm512_setzero_si512();
__m512i eights = _mm512_setzero_si512();
__m512i sixteens = _mm512_setzero_si512();
__m512i twosA, twosB, foursA, foursB, eightsA, eightsB;
uint64_t i = 0;
uint64_t limit = size - size % 16;
uint64_t* cnt64;
for(; i < limit; i += 16)
{
CSA512(&twosA, &ones, ones, data[i+0], data[i+1]);
CSA512(&twosB, &ones, ones, data[i+2], data[i+3]);
CSA512(&foursA, &twos, twos, twosA, twosB);
CSA512(&twosA, &ones, ones, data[i+4], data[i+5]);
CSA512(&twosB, &ones, ones, data[i+6], data[i+7]);
CSA512(&foursB, &twos, twos, twosA, twosB);
CSA512(&eightsA, &fours, fours, foursA, foursB);
CSA512(&twosA, &ones, ones, data[i+8], data[i+9]);
CSA512(&twosB, &ones, ones, data[i+10], data[i+11]);
CSA512(&foursA, &twos, twos, twosA, twosB);
CSA512(&twosA, &ones, ones, data[i+12], data[i+13]);
CSA512(&twosB, &ones, ones, data[i+14], data[i+15]);
CSA512(&foursB, &twos, twos, twosA, twosB);
CSA512(&eightsB, &fours, fours, foursA, foursB);
CSA512(&sixteens, &eights, eights, eightsA, eightsB);
cnt = _mm512_add_epi64(cnt, popcnt512(sixteens));
}
cnt = _mm512_slli_epi64(cnt, 4);
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(eights), 3));
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(fours), 2));
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(twos), 1));
cnt = _mm512_add_epi64(cnt, popcnt512(ones));
for(; i < size; i++)
cnt = _mm512_add_epi64(cnt, popcnt512(data[i]));
cnt64 = (uint64_t*) &cnt;
return cnt64[0] +
cnt64[1] +
cnt64[2] +
cnt64[3] +
cnt64[4] +
cnt64[5] +
cnt64[6] +
cnt64[7];
}
/* Align memory to 64 bytes boundary */
static inline void align_avx512(const uint8_t** p, uint64_t* size, uint64_t* cnt)
{
for (; (uintptr_t) *p % 8; (*p)++)
{
*cnt += popcnt64(**p);
*size -= 1;
}
for (; (uintptr_t) *p % 64; (*p) += 8)
{
*cnt += popcnt64(
*(const uint64_t*) *p);
*size -= 8;
}
}
#endif
/* x86 CPUs */
#if defined(X86_OR_X64)
/* Align memory to 8 bytes boundary */
static inline void align_8(const uint8_t** p, uint64_t* size, uint64_t* cnt)
{
for (; *size > 0 && (uintptr_t) *p % 8; (*p)++)
{
*cnt += popcount64(**p);
*size -= 1;
}
}
static inline uint64_t popcount64_unrolled(const uint64_t* data, uint64_t size)
{
uint64_t i = 0;
uint64_t limit = size - size % 4;
uint64_t cnt = 0;
for (; i < limit; i += 4)
{
cnt += popcount64(data[i+0]);
cnt += popcount64(data[i+1]);
cnt += popcount64(data[i+2]);
cnt += popcount64(data[i+3]);
}
for (; i < size; i++)
cnt += popcount64(data[i]);
return cnt;
}
/*
* Count the number of 1 bits in the data array
* @data: An array
* @size: Size of data in bytes
*/
static inline uint64_t popcnt(const void* data, uint64_t size)
{
const uint8_t* ptr = (const uint8_t*) data;
uint64_t cnt = 0;
uint64_t i;
#if defined(HAVE_CPUID)
#if defined(__cplusplus)
/* C++11 thread-safe singleton */
static const int cpuid = get_cpuid();
#else
static int cpuid_ = -1;
int cpuid = cpuid_;
if (cpuid == -1)
{
cpuid = get_cpuid();
#if defined(_MSC_VER)
_InterlockedCompareExchange(&cpuid_, cpuid, -1);
#else
__sync_val_compare_and_swap(&cpuid_, -1, cpuid);
#endif
}
#endif
#endif
#if defined(HAVE_AVX512)
/* AVX512 requires arrays >= 1024 bytes */
if ((cpuid & bit_AVX512) &&
size >= 1024)
{
align_avx512(&ptr, &size, &cnt);
cnt += popcnt_avx512((const __m512i*) ptr, size / 64);
ptr += size - size % 64;
size = size % 64;
}
#endif
#if defined(HAVE_AVX2)
/* AVX2 requires arrays >= 512 bytes */
if ((cpuid & bit_AVX2) &&
size >= 512)
{
align_avx2(&ptr, &size, &cnt);
cnt += popcnt_avx2((const __m256i*) ptr, size / 32);
ptr += size - size % 32;
size = size % 32;
}
#endif
#if defined(HAVE_POPCNT)
if (cpuid & bit_POPCNT)
{
cnt += popcnt64_unrolled((const uint64_t*) ptr, size / 8);
ptr += size - size % 8;
size = size % 8;
for (i = 0; i < size; i++)
cnt += popcnt64(ptr[i]);
return cnt;
}
#endif
/* pure integer popcount algorithm */
if (size >= 8)
{
align_8(&ptr, &size, &cnt);
cnt += popcount64_unrolled((const uint64_t*) ptr, size / 8);
ptr += size - size % 8;
size = size % 8;
}
/* pure integer popcount algorithm */
for (i = 0; i < size; i++)
cnt += popcount64(ptr[i]);
return cnt;
}
#elif defined(__ARM_NEON) || \
defined(__aarch64__)
#include <arm_neon.h>
/* Align memory to 8 bytes boundary */
static inline void align_8(const uint8_t** p, uint64_t* size, uint64_t* cnt)
{
for (; *size > 0 && (uintptr_t) *p % 8; (*p)++)
{
*cnt += popcnt64(**p);
*size -= 1;
}
}
static inline uint64x2_t vpadalq(uint64x2_t sum, uint8x16_t t)
{
return vpadalq_u32(sum, vpaddlq_u16(vpaddlq_u8(t)));
}
/*
* Count the number of 1 bits in the data array
* @data: An array
* @size: Size of data in bytes
*/
static inline uint64_t popcnt(const void* data, uint64_t size)
{
uint64_t cnt = 0;
uint64_t chunk_size = 64;
const uint8_t* ptr = (const uint8_t*) data;
if (size >= chunk_size)
{
uint64_t i = 0;
uint64_t iters = size / chunk_size;
uint64x2_t sum = vcombine_u64(vcreate_u64(0), vcreate_u64(0));
uint8x16_t zero = vcombine_u8(vcreate_u8(0), vcreate_u8(0));
do
{
uint8x16_t t0 = zero;
uint8x16_t t1 = zero;
uint8x16_t t2 = zero;
uint8x16_t t3 = zero;
/*
* After every 31 iterations we need to add the
* temporary sums (t0, t1, t2, t3) to the total sum.
* We must ensure that the temporary sums <= 255
* and 31 * 8 bits = 248 which is OK.
*/
uint64_t limit = (i + 31 < iters) ? i + 31 : iters;
/* Each iteration processes 64 bytes */
for (; i < limit; i++)
{
uint8x16x4_t input = vld4q_u8(ptr);
ptr += chunk_size;
t0 = vaddq_u8(t0, vcntq_u8(input.val[0]));
t1 = vaddq_u8(t1, vcntq_u8(input.val[1]));
t2 = vaddq_u8(t2, vcntq_u8(input.val[2]));
t3 = vaddq_u8(t3, vcntq_u8(input.val[3]));
}
sum = vpadalq(sum, t0);
sum = vpadalq(sum, t1);
sum = vpadalq(sum, t2);
sum = vpadalq(sum, t3);
}
while (i < iters);
uint64_t tmp[2];
vst1q_u64(tmp, sum);
cnt += tmp[0];
cnt += tmp[1];
}
size %= chunk_size;
align_8(&ptr, &size, &cnt);
const uint64_t* ptr64 = (const uint64_t*) ptr;
uint64_t iters = size / 8;
for (uint64_t i = 0; i < iters; i++)
cnt += popcnt64(ptr64[i]);
ptr += size - size % 8;
size = size % 8;
for (uint64_t i = 0; i < size; i++)
cnt += popcnt64(ptr[i]);
return cnt;
}
/* all other CPUs */
#else
/* Align memory to 8 bytes boundary */
static inline void align_8(const uint8_t** p, uint64_t* size, uint64_t* cnt)
{
for (; *size > 0 && (uintptr_t) *p % 8; (*p)++)
{
*cnt += popcnt64(**p);
*size -= 1;
}
}
/*
* Count the number of 1 bits in the data array
* @data: An array
* @size: Size of data in bytes
*/
static inline uint64_t popcnt(const void* data, uint64_t size)
{
const uint8_t* ptr = (const uint8_t*) data;
uint64_t cnt = 0;
uint64_t i;
align_8(&ptr, &size, &cnt);
cnt += popcnt64_unrolled((const uint64_t*) ptr, size / 8);
ptr += size - size % 8;
size = size % 8;
for (i = 0; i < size; i++)
cnt += popcnt64(ptr[i]);
return cnt;
}
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* LIBPOPCNT_H */

View File

@@ -57,3 +57,27 @@ Openconsole has three configuration types:
- AuditMode
AuditMode is an experimental mode that enables some additional static analysis from CppCoreCheck.
## Updating Nuget package references
Certain Nuget package references in this project, like `Microsoft.UI.Xaml`, must be updated outside of the Visual Studio NuGet package manager. This can be done using the snippet below.
> Note that to run this snippet, you need to use WSL as the command uses `sed`.
To update the version of a given package, use the following snippet
`git grep -z -l $PackageName | xargs -0 sed -i -e 's/$OldVersionNumber/$NewVersionNumber/g'`
where:
- `$PackageName` is the name of the package, e.g. Microsoft.UI.Xaml
- `$OldVersionNumber` is the version number currently used, e.g. 2.3.191217003-prerelease
- `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.4.200117003-prerelease
Example usage:
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.3.191217003-prerelease/2.4.200117003-prerelease/g'`
## Using .nupkg files instead of downloaded Nuget packages
If you want to use .nupkg files instead of the downloaded Nuget package, you can do this with the following steps:
1. Open the Nuget.config file and uncomment line 8 ("Static Package Dependencies")
2. Create the folder /dep/packages
3. Put your .nupkg files in /dep/packages
4. If you are using different versions than those already being used, you need to update the references as well. How to do that is explained under "Updating Nuget package references".

View File

@@ -39,7 +39,7 @@ Properties listed below are specific to each unique profile.
| `colorScheme` | Optional | String | `Campbell` | Name of the terminal color scheme to use. Color schemes are defined under `schemes`. |
| `colorTable` | Optional | Array[String] | | Array of colors used in the profile if `colorscheme` is not set. Array follows the format defined in `schemes`. |
| `commandline` | Optional | String | | Executable used in the profile. |
| `cursorColor` | Optional | String | `#FFFFFF` | Sets the cursor color for the profile. Uses hex color format: `"#rrggbb"`. |
| `cursorColor` | Optional | String | | Sets the cursor color of the profile. Overrides `cursorColor` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `cursorHeight` | Optional | Integer | | Sets the percentage height of the cursor starting from the bottom. Only works when `cursorShape` is set to `"vintage"`. Accepts values from 25-100. |
| `cursorShape` | Optional | String | `bar` | Sets the cursor shape for the profile. Possible values: `"vintage"` ( &#x2583; ), `"bar"` ( &#x2503; ), `"underscore"` ( &#x2581; ), `"filledBox"` ( &#x2588; ), `"emptyBox"` ( &#x25AF; ) |
| `fontFace` | Optional | String | `Consolas` | Name of the font face used in the profile. We will try to fallback to Consolas if this can't be found or is invalid. |
@@ -68,6 +68,7 @@ Properties listed below are specific to each color scheme. [ColorTool](https://g
| `foreground` | _Required_ | String | Sets the foreground color of the color scheme. |
| `background` | _Required_ | String | Sets the background color of the color scheme. |
| `selectionBackground` | Optional | String | Sets the selection background color of the color scheme. |
| `cursorColor` | Optional | String | Sets the cursor color of the color scheme. |
| `black` | _Required_ | String | Sets the color used as ANSI black. |
| `blue` | _Required_ | String | Sets the color used as ANSI blue. |
| `brightBlack` | _Required_ | String | Sets the color used as ANSI bright black. |
@@ -110,31 +111,31 @@ For commands with arguments:
| Command | Command Description | Action (*=required) | Action Arguments | Argument Descriptions |
| ------- | ------------------- | ------ | ---------------- | ----------------- |
| closePane | Close the active pane. | | | |
| closeTab | Close the current tab. | | | |
| closeWindow | Close the current window and all tabs within it. | | | |
| copy | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
| decreaseFontSize | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
| duplicateTab | Make a copy and open the current tab. | | | |
| find | Open the search dialog box. | | | |
| increaseFontSize | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
| moveFocus | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| newTab | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| nextTab | Open the tab to the right of the current one. | | | |
| openNewTabDropdown | Open the dropdown menu. | | | |
| openSettings | Open the settings file. | | | |
| paste | Insert the content that was copied onto the clipboard. | | | |
| prevTab | Open the tab to the left of the current one. | | | |
| resetFontSize | Reset the text size to the default value. | | | |
| resizePane | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| scrollDown | Move the screen down. | | | |
| scrollUp | Move the screen up. | | | |
| scrollUpPage | Move the screen up a whole page. | | | |
| scrollDownPage | Move the screen down a whole page. | | | |
| splitPane | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
| switchToTab | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| toggleFullscreen | Switch between fullscreen and default window sizes. | | | |
| unbound | Unbind the associated keys from any command. | | | |
| `closePane` | Close the active pane. | | | |
| `closeTab` | Close the current tab. | | | |
| `closeWindow` | Close the current window and all tabs within it. | | | |
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
| `decreaseFontSize` | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
| `duplicateTab` | Make a copy and open the current tab. | | | |
| `find` | Open the search dialog box. | | | |
| `increaseFontSize` | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| `newTab` | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| `nextTab` | Open the tab to the right of the current one. | | | |
| `openNewTabDropdown` | Open the dropdown menu. | | | |
| `openSettings` | Open the settings file. | | | |
| `paste` | Insert the content that was copied onto the clipboard. | | | |
| `prevTab` | Open the tab to the left of the current one. | | | |
| `resetFontSize` | Reset the text size to the default value. | | | |
| `resizePane` | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| `scrollDown` | Move the screen down. | | | |
| `scrollUp` | Move the screen up. | | | |
| `scrollUpPage` | Move the screen up a whole page. | | | |
| `scrollDownPage` | Move the screen down a whole page. | | | |
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
| `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |
| `unbound` | Unbind the associated keys from any command. | | | |
### Accepted Modifiers and Keys

View File

@@ -4,8 +4,9 @@
"title": "Microsoft's Windows Terminal Settings Profile Schema'",
"definitions": {
"KeyChordSegment": {
"pattern": "^(?<modifier>(ctrl|alt|shift)\\+?((ctrl|alt|shift)(?<!\\2)\\+?)?((ctrl|alt|shift)(?<!\\2|\\4))?\\+?)?(?<key>[^+\\s]+?)?(?<=[^+\\s])$",
"type": "string"
"pattern": "^(?<modifier>(ctrl|alt|shift)(?:\\+(ctrl|alt|shift)(?<!\\2))?(?:\\+(ctrl|alt|shift)(?<!\\2|\\3))?\\+)?(?<key>[^\\s+]|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
"type": "string",
"description": "The string should fit the format \"[ctrl+][alt+][shift+]<keyName>\", where each modifier is optional, separated by + symbols, and keyName is either one of the names listed in the table below, or any single key character. The string should be written in full lowercase.\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
},
"Color": {
"default": "#",
@@ -554,8 +555,7 @@
},
"cursorColor": {
"$ref": "#/definitions/Color",
"default": "#FFFFFF",
"description": "Sets the cursor color for the profile. Uses hex color format: \"#rrggbb\"."
"description": "Sets the cursor color of the profile. Overrides cursor color set in color scheme if colorscheme is set. Uses hex color format: \"#rrggbb\"."
},
"cursorHeight": {
"description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 25-100.",
@@ -746,6 +746,11 @@
"$ref": "#/definitions/Color",
"description": "Sets the color used as ANSI bright yellow."
},
"cursorColor": {
"$ref": "#/definitions/Color",
"default": "#FFFFFF",
"description": "Sets the cursor color of the color scheme."
},
"cyan": {
"$ref": "#/definitions/Color",
"description": "Sets the color used as ANSI cyan."

View File

@@ -30,6 +30,7 @@ Assuming that you've installed cmder into `%CMDER_ROOT%`:
{
"commandline" : "cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"",
"name" : "cmder",
"icon" : "%CMDER_ROOT%/icons/cmder.ico",
"startingDirectory" : "%USERPROFILE%"
}
```
@@ -77,4 +78,17 @@ Assuming that you've installed Git Bash into `C:/Program Files/Git`:
}
````
## MSYS2
Assuming that you've installed MSYS2 into `C:/msys64`:
```json
{
"name" : "MSYS2",
"commandline" : "C:/msys64/msys2_shell.cmd -defterm -no-start -mingw64",
"icon": "C:/msys64/msys2.ico",
"startingDirectory" : "C:/msys64/home/user"
}
````
<!-- Adding a tool here? Make sure to add it in alphabetical order! -->

View File

@@ -31,7 +31,7 @@ public:
RowCellIterator(const ROW& row, const size_t start, const size_t length);
~RowCellIterator() = default;
RowCellIterator& operator=(const RowCellIterator& it) = default;
RowCellIterator& operator=(const RowCellIterator& it) = delete;
operator bool() const noexcept;

View File

@@ -37,7 +37,7 @@ public:
Cursor& operator=(const Cursor&) & = delete;
Cursor(Cursor&&) = default;
Cursor& operator=(Cursor&&) & = default;
Cursor& operator=(Cursor&&) & = delete;
bool HasMoved() const noexcept;
bool IsVisible() const noexcept;

View File

@@ -1260,6 +1260,94 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
return true;
}
// Method Description:
// - Update pos to be the beginning of the current glyph/character. This is used for accessibility
// Arguments:
// - pos - a COORD on the word you are currently on
// Return Value:
// - pos - The COORD for the first cell of the current glyph (inclusive)
const til::point TextBuffer::GetGlyphStart(const til::point pos) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
{
bufferSize.DecrementInBounds(resultPos, true);
}
return resultPos;
}
// Method Description:
// - Update pos to be the end of the current glyph/character. This is used for accessibility
// Arguments:
// - pos - a COORD on the word you are currently on
// Return Value:
// - pos - The COORD for the last cell of the current glyph (exclusive)
const til::point TextBuffer::GetGlyphEnd(const til::point pos) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
{
bufferSize.IncrementInBounds(resultPos, true);
}
// increment one more time to become exclusive
bufferSize.IncrementInBounds(resultPos, true);
return resultPos;
}
// Method Description:
// - Update pos to be the beginning of the next glyph/character. This is used for accessibility
// Arguments:
// - pos - a COORD on the word you are currently on
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
// Return Value:
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
// - pos - The COORD for the first cell of the current glyph (inclusive)
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) const
{
COORD resultPos = pos;
// try to move. If we can't, we're done.
const auto bufferSize = GetSize();
const bool success = bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
{
bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
}
pos = resultPos;
return success;
}
// Method Description:
// - Update pos to be the beginning of the previous glyph/character. This is used for accessibility
// Arguments:
// - pos - a COORD on the word you are currently on
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
// Return Value:
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
// - pos - The COORD for the first cell of the previous glyph (inclusive)
bool TextBuffer::MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive) const
{
COORD resultPos = pos;
// try to move. If we can't, we're done.
const auto bufferSize = GetSize();
const bool success = bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
{
bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
}
pos = resultPos;
return success;
}
// Method Description:
// - Determines the line-by-line rectangles based on two COORDs
// - expands the rectangles to support wide glyphs

View File

@@ -134,6 +134,11 @@ public:
bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const;
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;
const til::point GetGlyphStart(const til::point pos) const;
const til::point GetGlyphEnd(const til::point pos) const;
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
bool MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive = false) const;
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
class TextAndColor

View File

@@ -17,7 +17,7 @@
Version="1.0.0.0" />
<Properties>
<DisplayName>ms-resource:AppName</DisplayName>
<DisplayName>Windows Terminal</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>

View File

@@ -100,6 +100,7 @@ namespace TerminalAppLocalTests
"foreground": "#000000",
"background": "#010101",
"selectionBackground": "#010100",
"cursorColor": "#010001",
"red": "#010000",
"green": "#000100",
"blue": "#000001"
@@ -109,6 +110,7 @@ namespace TerminalAppLocalTests
"foreground": "#020202",
"background": "#030303",
"selectionBackground": "#020200",
"cursorColor": "#040004",
"red": "#020000",
"blue": "#000002"
@@ -118,6 +120,7 @@ namespace TerminalAppLocalTests
"foreground": "#040404",
"background": "#050505",
"selectionBackground": "#030300",
"cursorColor": "#060006",
"red": "#030000",
"green": "#000300"
})" };
@@ -130,8 +133,8 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"scheme0", scheme0._schemeName);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 1), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 1), scheme0._table[XTERM_BLUE_ATTR]);
@@ -143,6 +146,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 4, 0, 4), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 2, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0._table[XTERM_BLUE_ATTR]);
@@ -154,6 +158,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 0, 6), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 3, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 3, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0._table[XTERM_BLUE_ATTR]);

View File

@@ -70,6 +70,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestTerminalArgsForBinding);
TEST_METHOD(TestLayerProfileOnColorScheme);
TEST_METHOD(ValidateKeybindingsWarnings);
TEST_CLASS_SETUP(ClassSetup)
@@ -1468,7 +1470,7 @@ namespace TerminalAppLocalTests
CascadiaSettings settings{};
settings._ParseJsonString(settingsJson, false);
settings.LayerJson(settings._userSettings);
VERIFY_IS_FALSE(settings._profiles.empty(), 0);
VERIFY_IS_FALSE(settings._profiles.empty());
VERIFY_ARE_EQUAL(expectedPath, settings._profiles[0].GetExpandedIconPath());
}
void SettingsTests::TestProfileBackgroundImageWithEnvVar()
@@ -1489,7 +1491,7 @@ namespace TerminalAppLocalTests
CascadiaSettings settings{};
settings._ParseJsonString(settingsJson, false);
settings.LayerJson(settings._userSettings);
VERIFY_IS_FALSE(settings._profiles.empty(), 0);
VERIFY_IS_FALSE(settings._profiles.empty());
GlobalAppSettings globalSettings{};
auto terminalSettings = settings._profiles[0].CreateTerminalSettings(globalSettings.GetColorSchemes());
@@ -2092,6 +2094,75 @@ namespace TerminalAppLocalTests
}
}
void SettingsTests::TestLayerProfileOnColorScheme()
{
Log::Comment(NoThrowString().Format(
L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly."));
const std::string settings0String{ R"(
{
"profiles": [
{
"name" : "profile0",
"colorScheme": "schemeWithCursorColor"
},
{
"name" : "profile1",
"colorScheme": "schemeWithoutCursorColor"
},
{
"name" : "profile2",
"colorScheme": "schemeWithCursorColor",
"cursorColor": "#234567"
},
{
"name" : "profile3",
"colorScheme": "schemeWithoutCursorColor",
"cursorColor": "#345678"
},
{
"name" : "profile4",
"cursorColor": "#456789"
},
{
"name" : "profile5"
}
],
"schemes": [
{
"name": "schemeWithCursorColor",
"cursorColor": "#123456"
},
{
"name": "schemeWithoutCursorColor"
}
]
})" };
VerifyParseSucceeded(settings0String);
CascadiaSettings settings;
settings._ParseJsonString(settings0String, false);
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(6u, settings._profiles.size());
VERIFY_ARE_EQUAL(2u, settings._globals._colorSchemes.size());
auto terminalSettings0 = settings._profiles[0].CreateTerminalSettings(settings._globals._colorSchemes);
auto terminalSettings1 = settings._profiles[1].CreateTerminalSettings(settings._globals._colorSchemes);
auto terminalSettings2 = settings._profiles[2].CreateTerminalSettings(settings._globals._colorSchemes);
auto terminalSettings3 = settings._profiles[3].CreateTerminalSettings(settings._globals._colorSchemes);
auto terminalSettings4 = settings._profiles[4].CreateTerminalSettings(settings._globals._colorSchemes);
auto terminalSettings5 = settings._profiles[5].CreateTerminalSettings(settings._globals._colorSchemes);
VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0.CursorColor()); // from color scheme
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1.CursorColor()); // default
VERIFY_ARE_EQUAL(ARGB(0, 0x23, 0x45, 0x67), terminalSettings2.CursorColor()); // from profile (trumps color scheme)
VERIFY_ARE_EQUAL(ARGB(0, 0x34, 0x56, 0x78), terminalSettings3.CursorColor()); // from profile (not set in color scheme)
VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4.CursorColor()); // from profile (no color scheme)
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5.CursorColor()); // default
}
void SettingsTests::ValidateKeybindingsWarnings()
{
const std::string badSettings{ R"(
@@ -2134,5 +2205,4 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(2));
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(3));
}
}

View File

@@ -289,7 +289,7 @@ HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void**
parentHwnd,
nullptr,
nullptr,
0);
nullptr);
auto _terminal = std::make_unique<HwndTerminal>(_hostWindow);
RETURN_IF_FAILED(_terminal->Initialize());

View File

@@ -516,7 +516,7 @@ std::vector<::TerminalApp::SettingsLoadWarnings> winrt::TerminalApp::implementat
warnings.insert(warnings.end(), parseWarnings.begin(), parseWarnings.end());
// if an arg parser was registered, but failed, bail
if (args == nullptr)
if (pfn && args == nullptr)
{
continue;
}

View File

@@ -34,6 +34,7 @@ namespace TerminalAppLocalTests
namespace TerminalAppUnitTests
{
class DynamicProfileTests;
class JsonTests;
};
namespace TerminalApp
@@ -124,4 +125,5 @@ private:
friend class TerminalAppLocalTests::KeyBindingsTests;
friend class TerminalAppLocalTests::TabTests;
friend class TerminalAppUnitTests::DynamicProfileTests;
friend class TerminalAppUnitTests::JsonTests;
};

View File

@@ -704,7 +704,7 @@ ColorScheme* CascadiaSettings::_FindMatchingColorScheme(const Json::Value& schem
bool CascadiaSettings::_IsPackaged()
{
UINT32 length = 0;
LONG rc = GetCurrentPackageFullName(&length, NULL);
LONG rc = GetCurrentPackageFullName(&length, nullptr);
return rc != APPMODEL_ERROR_NO_PACKAGE;
}
@@ -724,15 +724,15 @@ void CascadiaSettings::_WriteSettings(const std::string_view content)
wil::unique_hfile hOut{ CreateFileW(pathToSettingsFile.c_str(),
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL) };
nullptr) };
if (!hOut)
{
THROW_LAST_ERROR();
}
THROW_LAST_ERROR_IF(!WriteFile(hOut.get(), content.data(), gsl::narrow<DWORD>(content.size()), 0, 0));
THROW_LAST_ERROR_IF(!WriteFile(hOut.get(), content.data(), gsl::narrow<DWORD>(content.size()), nullptr, nullptr));
}
// Method Description:
@@ -853,7 +853,7 @@ std::wstring CascadiaSettings::GetSettingsPath(const bool useRoamingPath)
// the new AppModel paths (Packages/xxx/RoamingState, etc.) for standard path requests.
// Using this flag allows us to avoid Windows.Storage.ApplicationData completely.
const auto knowFolderId = useRoamingPath ? FOLDERID_RoamingAppData : FOLDERID_LocalAppData;
if (FAILED(SHGetKnownFolderPath(knowFolderId, KF_FLAG_FORCE_APP_DATA_REDIRECTION, 0, &localAppDataFolder)))
if (FAILED(SHGetKnownFolderPath(knowFolderId, KF_FLAG_FORCE_APP_DATA_REDIRECTION, nullptr, &localAppDataFolder)))
{
THROW_LAST_ERROR();
}

View File

@@ -18,6 +18,7 @@ static constexpr std::string_view TableKey{ "colors" };
static constexpr std::string_view ForegroundKey{ "foreground" };
static constexpr std::string_view BackgroundKey{ "background" };
static constexpr std::string_view SelectionBackgroundKey{ "selectionBackground" };
static constexpr std::string_view CursorColorKey{ "cursorColor" };
static constexpr std::array<std::string_view, 16> TableColors = {
"black",
"red",
@@ -42,16 +43,18 @@ ColorScheme::ColorScheme() :
_table{},
_defaultForeground{ DEFAULT_FOREGROUND_WITH_ALPHA },
_defaultBackground{ DEFAULT_BACKGROUND_WITH_ALPHA },
_selectionBackground{ DEFAULT_FOREGROUND }
_selectionBackground{ DEFAULT_FOREGROUND },
_cursorColor{ DEFAULT_CURSOR_COLOR }
{
}
ColorScheme::ColorScheme(std::wstring name, COLORREF defaultFg, COLORREF defaultBg) :
ColorScheme::ColorScheme(std::wstring name, COLORREF defaultFg, COLORREF defaultBg, COLORREF cursorColor) :
_schemeName{ name },
_table{},
_defaultForeground{ defaultFg },
_defaultBackground{ defaultBg },
_selectionBackground{ DEFAULT_FOREGROUND }
_selectionBackground{ DEFAULT_FOREGROUND },
_cursorColor{ cursorColor }
{
}
@@ -71,6 +74,7 @@ void ColorScheme::ApplyScheme(TerminalSettings terminalSettings) const
terminalSettings.DefaultForeground(_defaultForeground);
terminalSettings.DefaultBackground(_defaultBackground);
terminalSettings.SelectionBackground(_selectionBackground);
terminalSettings.CursorColor(_cursorColor);
auto const tableCount = gsl::narrow_cast<int>(_table.size());
for (int i = 0; i < tableCount; i++)
@@ -92,6 +96,7 @@ Json::Value ColorScheme::ToJson() const
root[JsonKey(ForegroundKey)] = Utils::ColorToHexString(_defaultForeground);
root[JsonKey(BackgroundKey)] = Utils::ColorToHexString(_defaultBackground);
root[JsonKey(SelectionBackgroundKey)] = Utils::ColorToHexString(_selectionBackground);
root[JsonKey(CursorColorKey)] = Utils::ColorToHexString(_cursorColor);
int i = 0;
for (const auto& colorName : TableColors)
@@ -166,6 +171,11 @@ void ColorScheme::LayerJson(const Json::Value& json)
const auto color = Utils::ColorFromHexString(sbString.asString());
_selectionBackground = color;
}
if (auto sbString{ json[JsonKey(CursorColorKey)] })
{
const auto color = Utils::ColorFromHexString(sbString.asString());
_cursorColor = color;
}
// Legacy Deserialization. Leave in place to allow forward compatibility
if (auto table{ json[JsonKey(TableKey)] })
@@ -220,6 +230,11 @@ COLORREF ColorScheme::GetSelectionBackground() const noexcept
return _selectionBackground;
}
COLORREF ColorScheme::GetCursorColor() const noexcept
{
return _cursorColor;
}
// Method Description:
// - Parse the name from the JSON representation of a ColorScheme.
// Arguments:

View File

@@ -35,7 +35,7 @@ class TerminalApp::ColorScheme
{
public:
ColorScheme();
ColorScheme(std::wstring name, COLORREF defaultFg, COLORREF defaultBg);
ColorScheme(std::wstring name, COLORREF defaultFg, COLORREF defaultBg, COLORREF cursorColor);
~ColorScheme();
void ApplyScheme(winrt::Microsoft::Terminal::Settings::TerminalSettings terminalSettings) const;
@@ -50,6 +50,7 @@ public:
COLORREF GetForeground() const noexcept;
COLORREF GetBackground() const noexcept;
COLORREF GetSelectionBackground() const noexcept;
COLORREF GetCursorColor() const noexcept;
static std::optional<std::wstring> GetNameFromJson(const Json::Value& json);
@@ -59,6 +60,7 @@ private:
COLORREF _defaultForeground;
COLORREF _defaultBackground;
COLORREF _selectionBackground;
COLORREF _cursorColor;
friend class TerminalAppLocalTests::SettingsTests;
friend class TerminalAppLocalTests::ColorSchemeTests;

View File

@@ -262,22 +262,14 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
_defaultProfile = guid;
}
if (auto alwaysShowTabs{ json[JsonKey(AlwaysShowTabsKey)] })
{
_alwaysShowTabs = alwaysShowTabs.asBool();
}
if (auto confirmCloseAllTabs{ json[JsonKey(ConfirmCloseAllKey)] })
{
_confirmCloseAllTabs = confirmCloseAllTabs.asBool();
}
if (auto initialRows{ json[JsonKey(InitialRowsKey)] })
{
_initialRows = initialRows.asInt();
}
if (auto initialCols{ json[JsonKey(InitialColsKey)] })
{
_initialCols = initialCols.asInt();
}
JsonUtils::GetBool(json, AlwaysShowTabsKey, _alwaysShowTabs);
JsonUtils::GetBool(json, ConfirmCloseAllKey, _confirmCloseAllTabs);
JsonUtils::GetInt(json, InitialRowsKey, _initialRows);
JsonUtils::GetInt(json, InitialColsKey, _initialCols);
if (auto rowsToScroll{ json[JsonKey(RowsToScrollKey)] })
{
//if it's not an int we fall back to setting it to 0, which implies using the system setting. This will be the case if it's set to "system"
@@ -290,29 +282,19 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
_rowsToScroll = 0;
}
}
if (auto initialPosition{ json[JsonKey(InitialPositionKey)] })
{
_ParseInitialPosition(GetWstringFromJson(initialPosition), _initialX, _initialY);
}
if (auto showTitleInTitlebar{ json[JsonKey(ShowTitleInTitlebarKey)] })
{
_showTitleInTitlebar = showTitleInTitlebar.asBool();
}
if (auto showTabsInTitlebar{ json[JsonKey(ShowTabsInTitlebarKey)] })
{
_showTabsInTitlebar = showTabsInTitlebar.asBool();
}
JsonUtils::GetBool(json, ShowTitleInTitlebarKey, _showTitleInTitlebar);
if (auto wordDelimiters{ json[JsonKey(WordDelimitersKey)] })
{
_wordDelimiters = GetWstringFromJson(wordDelimiters);
}
JsonUtils::GetBool(json, ShowTabsInTitlebarKey, _showTabsInTitlebar);
if (auto copyOnSelect{ json[JsonKey(CopyOnSelectKey)] })
{
_copyOnSelect = copyOnSelect.asBool();
}
JsonUtils::GetWstring(json, WordDelimitersKey, _wordDelimiters);
JsonUtils::GetBool(json, CopyOnSelectKey, _copyOnSelect);
if (auto launchMode{ json[JsonKey(LaunchModeKey)] })
{
@@ -341,10 +323,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
}
if (auto snapToGridOnResize{ json[JsonKey(SnapToGridOnResizeKey)] })
{
_SnapToGridOnResize = snapToGridOnResize.asBool();
}
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
}
// Method Description:

View File

@@ -52,8 +52,74 @@ void TerminalApp::JsonUtils::GetOptionalDouble(const Json::Value& json,
const auto conversionFn = [](const Json::Value& value) -> double {
return value.asFloat();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isNumeric();
};
GetOptionalValue(json,
key,
target,
conversionFn);
conversionFn,
validationFn);
}
void TerminalApp::JsonUtils::GetInt(const Json::Value& json,
std::string_view key,
int& target)
{
const auto conversionFn = [](const Json::Value& value) -> int {
return value.asInt();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isInt();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetUInt(const Json::Value& json,
std::string_view key,
uint32_t& target)
{
const auto conversionFn = [](const Json::Value& value) -> uint32_t {
return value.asUInt();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isUInt();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetDouble(const Json::Value& json,
std::string_view key,
double& target)
{
const auto conversionFn = [](const Json::Value& value) -> double {
return value.asFloat();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isNumeric();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetBool(const Json::Value& json,
std::string_view key,
bool& target)
{
const auto conversionFn = [](const Json::Value& value) -> bool {
return value.asBool();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isBool();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetWstring(const Json::Value& json,
std::string_view key,
std::wstring& target)
{
const auto conversionFn = [](const Json::Value& value) -> std::wstring {
return GetWstringFromJson(value);
};
GetValue(json, key, target, conversionFn, nullptr);
}

View File

@@ -46,24 +46,99 @@ namespace TerminalApp::JsonUtils
// - target: the optional object to receive the value from json
// - conversion: a std::function<T(const Json::Value&)> which can be used to
// convert the Json::Value to the appropriate type.
// - validation: optional, if provided, will be called first to ensure that
// the json::value is of the correct type before attempting to call
// `conversion`.
// Return Value:
// - <none>
template<typename T, typename F>
void GetOptionalValue(const Json::Value& json,
std::string_view key,
std::optional<T>& target,
F&& conversion)
F&& conversion,
const std::function<bool(const Json::Value&)>& validation = nullptr)
{
if (json.isMember(JsonKey(key)))
{
if (auto jsonVal{ json[JsonKey(key)] })
{
target = conversion(jsonVal);
if (validation == nullptr || validation(jsonVal))
{
target = conversion(jsonVal);
}
}
else
{
// This branch is hit when the json object contained the key,
// but the key was set to `null`. In this case, explicitly clear
// the target.
target = std::nullopt;
}
}
}
// Method Description:
// - Helper that can be used for retrieving a value from a json
// object, and parsing it's value to set on a given target object.
// - If the key we're looking for _doesn't_ exist in the json object,
// we'll leave the target object unmodified.
// - If the key exists in the json object, we'll use the provided
// `validation` function to ensure that the json value is of the
// correct type.
// - If we successfully validate the json value type (or no validation
// function was provided), then we'll use `conversion` to parse the
// value and place the result into `target`
// - Each caller should provide a conversion function that takes a
// Json::Value and returns an object of the same type as target.
// - Unlike GetOptionalValue, if the key exists but is set to `null`, we'll
// just ignore it.
// Arguments:
// - json: The json object to search for the given key
// - key: The key to look for in the json object
// - target: the optional object to receive the value from json
// - conversion: a std::function<T(const Json::Value&)> which can be used to
// convert the Json::Value to the appropriate type.
// - validation: optional, if provided, will be called first to ensure that
// the json::value is of the correct type before attempting to call
// `conversion`.
// Return Value:
// - <none>
template<typename T, typename F>
void GetValue(const Json::Value& json,
std::string_view key,
T& target,
F&& conversion,
const std::function<bool(const Json::Value&)>& validation = nullptr)
{
if (json.isMember(JsonKey(key)))
{
if (auto jsonVal{ json[JsonKey(key)] })
{
if (validation == nullptr || validation(jsonVal))
{
target = conversion(jsonVal);
}
}
}
}
void GetInt(const Json::Value& json,
std::string_view key,
int& target);
void GetUInt(const Json::Value& json,
std::string_view key,
uint32_t& target);
void GetDouble(const Json::Value& json,
std::string_view key,
double& target);
void GetBool(const Json::Value& json,
std::string_view key,
bool& target);
void GetWstring(const Json::Value& json,
std::string_view key,
std::wstring& target);
};

View File

@@ -803,10 +803,10 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
const auto paneSizes = _CalcChildrenSizes(rootSize.Width);
auto firstColDef = Controls::ColumnDefinition();
firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first));
firstColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
auto secondColDef = Controls::ColumnDefinition();
secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second));
secondColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
_root.ColumnDefinitions().Append(firstColDef);
_root.ColumnDefinitions().Append(secondColDef);
@@ -819,10 +819,10 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
const auto paneSizes = _CalcChildrenSizes(rootSize.Height);
auto firstRowDef = Controls::RowDefinition();
firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first));
firstRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
auto secondRowDef = Controls::RowDefinition();
secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second));
secondRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
_root.RowDefinitions().Append(firstRowDef);
_root.RowDefinitions().Append(secondRowDef);

View File

@@ -196,7 +196,7 @@ catch (...)
static void _accumulateStorePowerShellInstances(std::vector<PowerShellInstance>& out)
{
wil::unique_cotaskmem_string localAppDataFolder;
if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, 0, &localAppDataFolder)))
if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder)))
{
return;
}

View File

@@ -104,12 +104,12 @@ Profile::Profile(const std::optional<GUID>& guid) :
_defaultForeground{},
_defaultBackground{},
_selectionBackground{},
_cursorColor{},
_colorTable{},
_tabTitle{},
_suppressApplicationTitle{},
_historySize{ DEFAULT_HISTORY_SIZE },
_snapOnInput{ true },
_cursorColor{ DEFAULT_CURSOR_COLOR },
_cursorShape{ CursorStyle::Bar },
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
@@ -178,7 +178,6 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
}
terminalSettings.HistorySize(_historySize);
terminalSettings.SnapOnInput(_snapOnInput);
terminalSettings.CursorColor(_cursorColor);
terminalSettings.CursorHeight(_cursorHeight);
terminalSettings.CursorShape(_cursorShape);
@@ -228,6 +227,10 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
{
terminalSettings.SelectionBackground(_selectionBackground.value());
}
if (_cursorColor)
{
terminalSettings.CursorColor(_cursorColor.value());
}
if (_scrollbarState)
{
@@ -296,6 +299,10 @@ Json::Value Profile::ToJson() const
{
root[JsonKey(SelectionBackgroundKey)] = Utils::ColorToHexString(_selectionBackground.value());
}
if (_cursorColor)
{
root[JsonKey(CursorColorKey)] = Utils::ColorToHexString(_cursorColor.value());
}
if (_schemeName)
{
const auto scheme = winrt::to_string(_schemeName.value());
@@ -312,7 +319,6 @@ Json::Value Profile::ToJson() const
}
root[JsonKey(HistorySizeKey)] = _historySize;
root[JsonKey(SnapOnInputKey)] = _snapOnInput;
root[JsonKey(CursorColorKey)] = Utils::ColorToHexString(_cursorColor);
// Only add the cursor height property if we're a legacy-style cursor.
if (_cursorShape == CursorStyle::Vintage)
{
@@ -622,19 +628,11 @@ bool Profile::_ConvertJsonToBool(const Json::Value& json)
void Profile::LayerJson(const Json::Value& json)
{
// Profile-specific Settings
if (json.isMember(JsonKey(NameKey)))
{
auto name{ json[JsonKey(NameKey)] };
_name = GetWstringFromJson(name);
}
JsonUtils::GetWstring(json, NameKey, _name);
JsonUtils::GetOptionalGuid(json, GuidKey, _guid);
if (json.isMember(JsonKey(HiddenKey)))
{
auto hidden{ json[JsonKey(HiddenKey)] };
_hidden = hidden.asBool();
}
JsonUtils::GetBool(json, HiddenKey, _hidden);
// Core Settings
JsonUtils::GetOptionalColor(json, ForegroundKey, _defaultForeground);
@@ -643,6 +641,8 @@ void Profile::LayerJson(const Json::Value& json)
JsonUtils::GetOptionalColor(json, SelectionBackgroundKey, _selectionBackground);
JsonUtils::GetOptionalColor(json, CursorColorKey, _cursorColor);
JsonUtils::GetOptionalString(json, ColorSchemeKey, _schemeName);
// TODO:GH#1069 deprecate old settings key
JsonUtils::GetOptionalString(json, ColorSchemeKeyOld, _schemeName);
@@ -664,28 +664,14 @@ void Profile::LayerJson(const Json::Value& json)
i++;
}
}
if (json.isMember(JsonKey(HistorySizeKey)))
{
auto historySize{ json[JsonKey(HistorySizeKey)] };
// TODO:MSFT:20642297 - Use a sentinel value (-1) for "Infinite scrollback"
_historySize = historySize.asInt();
}
if (json.isMember(JsonKey(SnapOnInputKey)))
{
auto snapOnInput{ json[JsonKey(SnapOnInputKey)] };
_snapOnInput = snapOnInput.asBool();
}
if (json.isMember(JsonKey(CursorColorKey)))
{
auto cursorColor{ json[JsonKey(CursorColorKey)] };
const auto color = Utils::ColorFromHexString(cursorColor.asString());
_cursorColor = color;
}
if (json.isMember(JsonKey(CursorHeightKey)))
{
auto cursorHeight{ json[JsonKey(CursorHeightKey)] };
_cursorHeight = cursorHeight.asUInt();
}
// TODO:MSFT:20642297 - Use a sentinel value (-1) for "Infinite scrollback"
JsonUtils::GetInt(json, HistorySizeKey, _historySize);
JsonUtils::GetBool(json, SnapOnInputKey, _snapOnInput);
JsonUtils::GetUInt(json, CursorHeightKey, _cursorHeight);
if (json.isMember(JsonKey(CursorShapeKey)))
{
auto cursorShape{ json[JsonKey(CursorShapeKey)] };
@@ -696,46 +682,25 @@ void Profile::LayerJson(const Json::Value& json)
// Control Settings
JsonUtils::GetOptionalGuid(json, ConnectionTypeKey, _connectionType);
if (json.isMember(JsonKey(CommandlineKey)))
{
auto commandline{ json[JsonKey(CommandlineKey)] };
_commandline = GetWstringFromJson(commandline);
}
if (json.isMember(JsonKey(FontFaceKey)))
{
auto fontFace{ json[JsonKey(FontFaceKey)] };
_fontFace = GetWstringFromJson(fontFace);
}
if (json.isMember(JsonKey(FontSizeKey)))
{
auto fontSize{ json[JsonKey(FontSizeKey)] };
_fontSize = fontSize.asInt();
}
if (json.isMember(JsonKey(AcrylicTransparencyKey)))
{
auto acrylicTransparency{ json[JsonKey(AcrylicTransparencyKey)] };
_acrylicTransparency = acrylicTransparency.asFloat();
}
if (json.isMember(JsonKey(UseAcrylicKey)))
{
auto useAcrylic{ json[JsonKey(UseAcrylicKey)] };
_useAcrylic = useAcrylic.asBool();
}
if (json.isMember(JsonKey(SuppressApplicationTitleKey)))
{
auto suppressApplicationTitle{ json[JsonKey(SuppressApplicationTitleKey)] };
_suppressApplicationTitle = suppressApplicationTitle.asBool();
}
JsonUtils::GetWstring(json, CommandlineKey, _commandline);
JsonUtils::GetWstring(json, FontFaceKey, _fontFace);
JsonUtils::GetInt(json, FontSizeKey, _fontSize);
JsonUtils::GetDouble(json, AcrylicTransparencyKey, _acrylicTransparency);
JsonUtils::GetBool(json, UseAcrylicKey, _useAcrylic);
JsonUtils::GetBool(json, SuppressApplicationTitleKey, _suppressApplicationTitle);
if (json.isMember(JsonKey(CloseOnExitKey)))
{
auto closeOnExit{ json[JsonKey(CloseOnExitKey)] };
_closeOnExitMode = ParseCloseOnExitMode(closeOnExit);
}
if (json.isMember(JsonKey(PaddingKey)))
{
auto padding{ json[JsonKey(PaddingKey)] };
_padding = GetWstringFromJson(padding);
}
JsonUtils::GetWstring(json, PaddingKey, _padding);
JsonUtils::GetOptionalString(json, ScrollbarStateKey, _scrollbarState);

View File

@@ -142,12 +142,12 @@ private:
std::optional<uint32_t> _defaultForeground;
std::optional<uint32_t> _defaultBackground;
std::optional<uint32_t> _selectionBackground;
std::optional<uint32_t> _cursorColor;
std::array<uint32_t, COLOR_TABLE_SIZE> _colorTable;
std::optional<std::wstring> _tabTitle;
bool _suppressApplicationTitle;
int32_t _historySize;
bool _snapOnInput;
uint32_t _cursorColor;
uint32_t _cursorHeight;
winrt::Microsoft::Terminal::Settings::CursorStyle _cursorShape;

View File

@@ -19,6 +19,7 @@
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<!-- ========================= XAML files ======================== -->
<ItemGroup>
<!-- DON'T PUT XAML FILES HERE! Put them in TerminalAppLib.vcxproj -->
@@ -77,11 +78,17 @@
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<!-- A small helper for paths to the compiled cppwinrt projects -->
<_BinRoot Condition="'$(Platform)' != 'Win32'">$(OpenConsoleDir)$(Platform)\$(Configuration)\</_BinRoot>
<_BinRoot Condition="'$(Platform)' == 'Win32'">$(OpenConsoleDir)$(Configuration)\</_BinRoot>
</PropertyGroup>
<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')" />
<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('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>

View File

@@ -673,8 +673,9 @@ namespace winrt::TerminalApp::implementation
const RoutedEventArgs&)
{
const auto feedbackUriValue = RS_(L"FeedbackUriValue");
winrt::Windows::Foundation::Uri feedbackUri{ feedbackUriValue };
winrt::Windows::System::Launcher::LaunchUriAsync({ feedbackUriValue });
winrt::Windows::System::Launcher::LaunchUriAsync(feedbackUri);
}
// Method Description:

View File

@@ -63,13 +63,13 @@ std::vector<TerminalApp::Profile> WslDistroGenerator::GenerateProfiles()
nullptr,
&si,
&pi));
switch (WaitForSingleObject(pi.hProcess, INFINITE))
switch (WaitForSingleObject(pi.hProcess, 2000))
{
case WAIT_OBJECT_0:
break;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
THROW_HR(ERROR_CHILD_NOT_COMPLETE);
return profiles;
case WAIT_FAILED:
THROW_LAST_ERROR();
default:

View File

@@ -22,7 +22,6 @@
"startingDirectory": "%USERPROFILE%",
"closeOnExit": "graceful",
"colorScheme": "Campbell Powershell",
"cursorColor": "#FFFFFF",
"cursorShape": "bar",
"fontFace": "Consolas",
"fontSize": 12,
@@ -41,7 +40,6 @@
"startingDirectory": "%USERPROFILE%",
"closeOnExit": "graceful",
"colorScheme": "Campbell",
"cursorColor": "#FFFFFF",
"cursorShape": "bar",
"fontFace": "Consolas",
"fontSize": 12,
@@ -59,6 +57,7 @@
"name": "Campbell",
"foreground": "#CCCCCC",
"background": "#0C0C0C",
"cursorColor": "#FFFFFF",
"black": "#0C0C0C",
"red": "#C50F1F",
"green": "#13A10E",
@@ -80,6 +79,7 @@
"name": "Campbell Powershell",
"foreground": "#CCCCCC",
"background": "#012456",
"cursorColor": "#FFFFFF",
"black": "#0C0C0C",
"red": "#C50F1F",
"green": "#13A10E",
@@ -101,6 +101,7 @@
"name": "Vintage",
"foreground": "#C0C0C0",
"background": "#000000",
"cursorColor": "#FFFFFF",
"black": "#000000",
"red": "#800000",
"green": "#008000",
@@ -122,6 +123,7 @@
"name": "One Half Dark",
"foreground": "#DCDFE4",
"background": "#282C34",
"cursorColor": "#FFFFFF",
"black": "#282C34",
"red": "#E06C75",
"green": "#98C379",
@@ -143,6 +145,7 @@
"name": "One Half Light",
"foreground": "#383A42",
"background": "#FAFAFA",
"cursorColor": "#4F525D",
"black": "#383A42",
"red": "#E45649",
"green": "#50A14F",
@@ -164,6 +167,7 @@
"name": "Solarized Dark",
"foreground": "#839496",
"background": "#002B36",
"cursorColor": "#FFFFFF",
"black": "#073642",
"red": "#DC322F",
"green": "#859900",
@@ -185,6 +189,7 @@
"name": "Solarized Light",
"foreground": "#657B83",
"background": "#FDF6E3",
"cursorColor": "#002B36",
"black": "#073642",
"red": "#DC322F",
"green": "#859900",

View File

@@ -22,9 +22,11 @@
When we do this, the XBF ends up in resources.pri.
-->
<DisableEmbeddedXbf>false</DisableEmbeddedXbf>
<XamlComponentResourceLocation>nested</XamlComponentResourceLocation>
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<Import Project="..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<ItemDefinitionGroup>
<ClCompile>
@@ -282,39 +284,9 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<!-- Manually include MUX here, instead of importing its targets file. We need
to reference its winmd, but very specifically with the
CopyLocalSatelliteAssemblies and Private properties set to false, as to not
have projects including us double-including MUX's .winmd. The following blob
is taken straight from the MUX build targets, with the aforementioned
changes.-->
<PropertyGroup>
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXRoot>$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\</_MUXRoot>
<_MUXAppRoot>$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\</_MUXAppRoot>
</PropertyGroup>
<ItemGroup>
<!-- Microsoft.UI.XAML -->
<Reference Include="$(_MUXRoot)lib\uap10.0\Microsoft.UI.Xaml.winmd">
<Implementation>Microsoft.UI.Xaml.dll</Implementation>
<IsWinMDFile>true</IsWinMDFile>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<Private>true</Private>
</Reference>
<ReferenceCopyLocalPaths Include="$(_MUXRoot)runtimes\win10-$(Native-Platform)\native\Microsoft.UI.Xaml.dll" />
<ReferenceCopyLocalPaths Include="$(_MUXRoot)runtimes\win10-$(Native-Platform)\native\Microsoft.UI.Xaml.pri" />
<!-- Microsoft.UI.XAML.Application -->
<Reference Include="$(_MUXAppRoot)lib\uap10.0\Microsoft.Toolkit.Win32.UI.XamlHost.winmd">
<Implementation>Microsoft.Toolkit.Win32.UI.XamlHost.dll</Implementation>
<IsWinMDFile>true</IsWinMDFile>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<Private>false</Private>
</Reference>
<ReferenceCopyLocalPaths Include="$(_MUXAppRoot)lib\uap10.0\Microsoft.Toolkit.Win32.UI.XamlHost.*" />
<ReferenceCopyLocalPaths Include="$(_MUXAppRoot)runtimes\win10-$(Native-Platform)\native\Microsoft.Toolkit.Win32.UI.XamlHost.*" />
</ItemGroup>
<!-- End MUX import -->
<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')" />
<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>
@@ -322,6 +294,7 @@
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target>
<!--
By default, the PRI file will contain resource paths beginning with the
project name. Since we enabled XBF embedding, this *also* includes App.xbf.

View File

@@ -2,5 +2,5 @@
<packages>
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.3.191217003-prerelease" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
<package id="vcpkg-cpprestsdk" version="2.10.14" targetFramework="native" />
<package id="vcpkg-telnetpp" version="1.0.1" targetFramework="native" />
</packages>

View File

@@ -36,6 +36,7 @@
<Style x:Key="SearchBoxBackground" TargetType="StackPanel">
<Setter Property="Background" Value="#333333" />
</Style>
<Style x:Key="PathStyle" TargetType="PathIcon" />
<!-- TextBox colors !-->
<SolidColorBrush x:Key="TextControlBackground" Color="#333333"/>
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#B5B5B5"/>
@@ -93,6 +94,7 @@
<Style x:Key="SearchBoxBackground" TargetType="StackPanel">
<Setter Property="Background" Value="#CCCCCC" />
</Style>
<Style x:Key="PathStyle" TargetType="PathIcon" />
<!-- TextBox colors !-->
<SolidColorBrush x:Key="TextControlBackground" Color="#CCCCCC"/>
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#636363"/>
@@ -147,6 +149,9 @@
<Style x:Key="SearchBoxBackground" TargetType="StackPanel">
<Setter Property="Background" Value="{ThemeResource SystemColorWindowColor}" />
</Style>
<Style x:Key="PathStyle" TargetType="PathIcon">
<Setter Property="Foreground" Value="{ThemeResource SystemColorWindowTextColor}" />
</Style>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
@@ -183,7 +188,8 @@
<ToggleButton x:Name="CaseSensitivityButton"
x:Uid="SearchBox_CaseSensitivity"
Style="{StaticResource ToggleButtonStyle}">
<PathIcon Data="M8.87305 10H7.60156L6.5625 7.25195H2.40625L1.42871 10H0.150391L3.91016 0.197266H5.09961L8.87305 10ZM6.18652 6.21973L4.64844 2.04297C4.59831 1.90625 4.54818 1.6875 4.49805 1.38672H4.4707C4.42513 1.66471 4.37272 1.88346 4.31348 2.04297L2.78906 6.21973H6.18652ZM15.1826 10H14.0615V8.90625H14.0342C13.5465 9.74479 12.8288 10.1641 11.8809 10.1641C11.1836 10.1641 10.6367 9.97949 10.2402 9.61035C9.84831 9.24121 9.65234 8.7513 9.65234 8.14062C9.65234 6.83268 10.4225 6.07161 11.9629 5.85742L14.0615 5.56348C14.0615 4.37402 13.5807 3.7793 12.6191 3.7793C11.776 3.7793 11.015 4.06641 10.3359 4.64062V3.49219C11.0241 3.05469 11.8171 2.83594 12.7148 2.83594C14.36 2.83594 15.1826 3.70638 15.1826 5.44727V10ZM14.0615 6.45898L12.373 6.69141C11.8535 6.76432 11.4616 6.89421 11.1973 7.08105C10.9329 7.26335 10.8008 7.58919 10.8008 8.05859C10.8008 8.40039 10.9215 8.68066 11.1631 8.89941C11.4092 9.11361 11.735 9.2207 12.1406 9.2207C12.6966 9.2207 13.1546 9.02702 13.5146 8.63965C13.8792 8.24772 14.0615 7.75326 14.0615 7.15625V6.45898Z"/>
<PathIcon Data="M8.87305 10H7.60156L6.5625 7.25195H2.40625L1.42871 10H0.150391L3.91016 0.197266H5.09961L8.87305 10ZM6.18652 6.21973L4.64844 2.04297C4.59831 1.90625 4.54818 1.6875 4.49805 1.38672H4.4707C4.42513 1.66471 4.37272 1.88346 4.31348 2.04297L2.78906 6.21973H6.18652ZM15.1826 10H14.0615V8.90625H14.0342C13.5465 9.74479 12.8288 10.1641 11.8809 10.1641C11.1836 10.1641 10.6367 9.97949 10.2402 9.61035C9.84831 9.24121 9.65234 8.7513 9.65234 8.14062C9.65234 6.83268 10.4225 6.07161 11.9629 5.85742L14.0615 5.56348C14.0615 4.37402 13.5807 3.7793 12.6191 3.7793C11.776 3.7793 11.015 4.06641 10.3359 4.64062V3.49219C11.0241 3.05469 11.8171 2.83594 12.7148 2.83594C14.36 2.83594 15.1826 3.70638 15.1826 5.44727V10ZM14.0615 6.45898L12.373 6.69141C11.8535 6.76432 11.4616 6.89421 11.1973 7.08105C10.9329 7.26335 10.8008 7.58919 10.8008 8.05859C10.8008 8.40039 10.9215 8.68066 11.1631 8.89941C11.4092 9.11361 11.735 9.2207 12.1406 9.2207C12.6966 9.2207 13.1546 9.02702 13.5146 8.63965C13.8792 8.24772 14.0615 7.75326 14.0615 7.15625V6.45898Z"
Style="{ThemeResource PathStyle}" />
</ToggleButton>
<Button x:Name="CloseButton"

View File

@@ -163,24 +163,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Get scale factor for view
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
// Set the selection layout bounds
Rect selectionRect = Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight);
// position textblock to cursor position
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
Canvas().SetTop(TextBlock(), ::base::ClampedNumeric<double>(clientCursorPos.Y));
// calculate FontSize in pixels from DIPs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
TextBlock().FontSize(fontSizePx);
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
const auto widthToTerminalEnd = Canvas().ActualWidth() - ::base::ClampedNumeric<double>(clientCursorPos.X);
TextBlock().MaxWidth(widthToTerminalEnd);
// Set the text block bounds
const auto yOffset = ::base::ClampedNumeric<float>(TextBlock().ActualHeight()) - fontHeight;
const auto textBottom = ::base::ClampedNumeric<float>(screenCursorPos.Y) + yOffset;
Rect selectionRect = Rect(screenCursorPos.X, textBottom, 0, fontHeight);
request.LayoutBounds().TextBounds(ScaleRect(selectionRect, scaleFactor));
// Set the control bounds of the whole control
Rect controlRect = Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight);
request.LayoutBounds().ControlBounds(ScaleRect(controlRect, scaleFactor));
// position textblock to cursor position
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
Canvas().SetTop(TextBlock(), ::base::ClampedNumeric<double>(clientCursorPos.Y));
TextBlock().Height(fontHeight);
// calculate FontSize in pixels from DIPs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
TextBlock().FontSize(fontSizePx);
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
}
// Method Description:
@@ -297,6 +300,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
try
{
// When a user deletes the last character in their current composition, some machines
// will fire a CompositionCompleted before firing a TextUpdating event that deletes the last character.
// The TextUpdating will have a lower StartCaretPosition, so in this scenario, _activeTextStart
// needs to update to be the StartCaretPosition.
// A known issue related to this behavior is that the last character that's deleted from a composition
// will get sent to the terminal before we receive the TextUpdate to delete the character.
// See GH #5054.
_activeTextStart = ::base::ClampMin(_activeTextStart, ::base::ClampedNumeric<size_t>(range.StartCaretPosition));
_inputBuffer = _inputBuffer.replace(
range.StartCaretPosition,
::base::ClampSub<size_t>(range.EndCaretPosition, range.StartCaretPosition),

View File

@@ -12,6 +12,7 @@
Visibility="Collapsed">
<TextBlock x:Name="TextBlock"
IsTextSelectionEnabled="false"
TextWrapping="Wrap"
TextDecorations="Underline" />
</Canvas>
</UserControl>

View File

@@ -724,6 +724,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
bool handled = false;
// Alt-Numpad# input will send us a character once the user releases Alt, so we should be ignoring the individual keydowns.
// The character will be sent through the TSFInputControl.
// See GH#1401 for more details
if (modifiers.IsAltPressed() && (e.OriginalKey() >= VirtualKey::NumberPad0 && e.OriginalKey() <= VirtualKey::NumberPad9))
{
e.Handled(true);
return;
}
// GH#2235: Terminal::Settings hasn't been modified to differentiate between AltGr and Ctrl+Alt yet.
// -> Don't check for key bindings if this is an AltGr key combination.
if (!modifiers.IsAltGrPressed())
@@ -1965,8 +1975,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
double height = rows * fFontHeight;
auto thickness = _ParseThicknessFromPadding(settings.Padding());
width += thickness.Left + thickness.Right;
height += thickness.Top + thickness.Bottom;
// GH#2061 - make sure to account for the size the padding _will be_ scaled to
width += scale * (thickness.Left + thickness.Right);
height += scale * (thickness.Top + thickness.Bottom);
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
}

View File

@@ -9,6 +9,7 @@
#include "TermControlAutomationPeer.g.cpp"
#include "XamlUiaTextRange.h"
#include "..\types\UiaTracing.h"
using namespace Microsoft::Console::Types;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
@@ -44,6 +45,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControlAutomationPeer::SignalSelectionChanged()
{
UiaTracing::Signal::SelectionChanged();
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
// The event that is raised when the text selection is modified.
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextSelectionChanged);
@@ -58,6 +60,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControlAutomationPeer::SignalTextChanged()
{
UiaTracing::Signal::TextChanged();
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
// The event that is raised when textual content is modified.
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextChanged);
@@ -72,9 +75,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControlAutomationPeer::SignalCursorChanged()
{
UiaTracing::Signal::CursorChanged();
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
// The event that is raised when the text was changed in an edit control.
RaiseAutomationEvent(AutomationEvents::TextEditTextChanged);
// Do NOT fire a TextEditTextChanged. Generally, an app on the other side
// will expect more information. Though you can dispatch that event
// on its own, it may result in a nullptr exception on the other side
// because no additional information was provided. Crashing the screen
// reader.
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextSelectionChanged);
});
}

View File

@@ -26,6 +26,7 @@
the default OutDir up one level, so the wapproj will be able to find it.
-->
<NoOutputRedirection>true</NoOutputRedirection>
<XamlComponentResourceLocation>nested</XamlComponentResourceLocation>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@@ -173,15 +173,17 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
{
return S_FALSE;
}
const auto dx = viewportSize.X - oldDimensions.X;
const auto dx = ::base::ClampSub(viewportSize.X, oldDimensions.X);
const auto oldTop = _mutableViewport.Top();
const short newBufferHeight = viewportSize.Y + _scrollbackLines;
const short newBufferHeight = ::base::ClampAdd(viewportSize.Y, _scrollbackLines);
COORD bufferSize{ viewportSize.X, newBufferHeight };
// Save cursor's relative height versus the viewport
const short sCursorHeightInViewportBefore = _buffer->GetCursor().GetPosition().Y - _mutableViewport.Top();
const short sCursorHeightInViewportBefore = ::base::ClampSub(_buffer->GetCursor().GetPosition().Y, _mutableViewport.Top());
// This will be used to determine where the viewport should be in the new buffer.
const short oldViewportTop = _mutableViewport.Top();
@@ -266,7 +268,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
const auto maxRow = std::max(newLastChar.Y, newCursorPos.Y);
const short proposedTopFromLastLine = ::base::saturated_cast<short>(maxRow - viewportSize.Y + 1);
const short proposedTopFromLastLine = ::base::ClampAdd(::base::ClampSub(maxRow, viewportSize.Y), 1);
const short proposedTopFromScrollback = newViewportTop;
short proposedTop = std::max(proposedTopFromLastLine,
@@ -294,7 +296,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
{
try
{
auto row = newTextBuffer->GetRowByOffset(::base::saturated_cast<short>(proposedTop - 1));
auto row = newTextBuffer->GetRowByOffset(::base::ClampSub(proposedTop, 1));
if (row.GetCharRow().WasWrapForced())
{
proposedTop--;
@@ -324,7 +326,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
const auto proposedBottom = newView.BottomExclusive();
if (proposedBottom > bufferSize.Y)
{
proposedTop = ::base::saturated_cast<short>(proposedTop - (proposedBottom - bufferSize.Y));
proposedTop = ::base::ClampSub(proposedTop, ::base::ClampSub(proposedBottom, bufferSize.Y));
}
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
@@ -339,7 +341,14 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
// If the old scrolloffset was 0, then we weren't scrolled back at all
// before, and shouldn't be now either.
_scrollOffset = originalOffsetWasZero ? 0 : _mutableViewport.Top() - newVisibleTop;
_scrollOffset = originalOffsetWasZero ? 0 : ::base::ClampSub(_mutableViewport.Top(), newVisibleTop);
// GH#5029 - make sure to InvalidateAll here, so that we'll paint the entire visible viewport.
try
{
_buffer->GetRenderTarget().TriggerRedrawAll();
}
CATCH_LOG();
_NotifyScrollEvent();
return S_OK;

View File

@@ -156,6 +156,7 @@ public:
#pragma region IUiaData
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
const bool IsSelectionActive() const noexcept override;
const bool IsBlockSelection() const noexcept override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const noexcept override;

View File

@@ -106,6 +106,11 @@ const bool Terminal::IsSelectionActive() const noexcept
return _selection.has_value();
}
const bool Terminal::IsBlockSelection() const noexcept
{
return _blockSelection;
}
// Method Description:
// - Checks if the CopyOnSelect setting is active
// Return Value:

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@@ -347,17 +347,14 @@ void ConptyRoundtripTests::WriteAFewSimpleLines()
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");
// Jump down to the fourth line because emitting spaces didn't do anything
// and we will skip to emitting the CCC segment.
expectedOutput.push_back("\x1b[4;1H");
expectedOutput.push_back("CCC");
// Cursor goes back on.
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyData(termTb);
@@ -458,14 +455,10 @@ void ConptyRoundtripTests::TestAdvancedWrapping()
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
// Without line breaking, write the remaining 20 chars
expectedOutput.push_back(R"(qrstuvwxyz{|}~!"#$%&)");
// Clear the rest of row 1
expectedOutput.push_back("\x1b[K");
// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
@@ -537,8 +530,6 @@ void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back("1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
@@ -601,8 +592,6 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
@@ -639,7 +628,7 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
// might change, but the buffer contents shouldn't.
// If they do change and these tests break, that's to be expected.
expectedOutput.push_back(std::string(TerminalViewWidth, 'A'));
expectedOutput.push_back("\x1b[1;80H");
// expectedOutput.push_back("\x1b[1;80H");
VERIFY_SUCCEEDED(renderer.PaintFrame());
@@ -665,8 +654,11 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
verifyData1(hostTb);
// expectedOutput.push_back(" ");
// expectedOutput.push_back("\x1b[1;80H");
expectedOutput.push_back("\x08");
expectedOutput.push_back(" ");
expectedOutput.push_back("\x1b[1;80H");
expectedOutput.push_back("\x08");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyData1(termTb);

View File

@@ -11,6 +11,9 @@
using namespace winrt::Microsoft::Terminal::Settings;
using namespace Microsoft::Terminal::Core;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalCoreUnitTests
{
@@ -21,66 +24,109 @@ namespace TerminalCoreUnitTests
{
TEST_CLASS(ScreenSizeLimitsTest);
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds)
{
DummyRenderTarget emptyRenderTarget;
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds);
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds);
// Negative values for initial visible row count or column count
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
Terminal negativeColumnsTerminal;
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
// Zero values are clamped to 1 as well.
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
Terminal zeroRowsTerminal;
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
}
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds)
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
const unsigned int visibleRowCount = 100;
DummyRenderTarget emptyRenderTarget;
// Zero history size is acceptable.
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
Terminal noHistoryTerminal;
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted");
// Negative history sizes are clamped to zero.
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
Terminal negativeHistorySizeTerminal;
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0");
// History size + initial visible rows == SHRT_MAX is acceptable.
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
Terminal maxHistorySizeTerminal;
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == SHRT_MAX - initial row count is accepted");
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
Terminal justTooBigHistorySizeTerminal;
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
// Ridiculously large history sizes are also clamped.
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
Terminal farTooBigHistorySizeTerminal;
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size that is far too large is clamped to SHRT_MAX - initial row count");
}
TEST_METHOD(ResizeIsClampedToBounds);
};
}
using namespace TerminalCoreUnitTests;
void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds()
{
DummyRenderTarget emptyRenderTarget;
// Negative values for initial visible row count or column count
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
Terminal negativeColumnsTerminal;
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
// Zero values are clamped to 1 as well.
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
Terminal zeroRowsTerminal;
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
}
void ScreenSizeLimitsTest::ScrollbackHistorySizeIsClampedToBounds()
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
const unsigned int visibleRowCount = 100;
DummyRenderTarget emptyRenderTarget;
// Zero history size is acceptable.
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
Terminal noHistoryTerminal;
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted");
// Negative history sizes are clamped to zero.
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
Terminal negativeHistorySizeTerminal;
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0");
// History size + initial visible rows == SHRT_MAX is acceptable.
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
Terminal maxHistorySizeTerminal;
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == SHRT_MAX - initial row count is accepted");
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
Terminal justTooBigHistorySizeTerminal;
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
// Ridiculously large history sizes are also clamped.
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
Terminal farTooBigHistorySizeTerminal;
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size that is far too large is clamped to SHRT_MAX - initial row count");
}
void ScreenSizeLimitsTest::ResizeIsClampedToBounds()
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
//
// This is a test for GH#2630, GH#2815.
const unsigned int initialVisibleColCount = 50;
const unsigned int initialVisibleRowCount = 50;
const auto historySize = SHRT_MAX - (initialVisibleRowCount * 2);
DummyRenderTarget emptyRenderTarget;
Log::Comment(L"Watch out - this test takes a while on debug, because "
L"ResizeWithReflow takes a while on debug. This is expected.");
auto settings = winrt::make<MockTermSettings>(historySize, initialVisibleRowCount, initialVisibleColCount);
Log::Comment(L"First create a terminal with fewer than SHRT_MAX lines");
Terminal terminal;
terminal.CreateFromSettings(settings, emptyRenderTarget);
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(historySize + initialVisibleRowCount));
Log::Comment(L"Resize the terminal to have exactly SHRT_MAX lines");
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount * 2 }));
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX));
Log::Comment(L"Resize the terminal to have MORE than SHRT_MAX lines - we should clamp to SHRT_MAX");
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount * 3 }));
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX));
Log::Comment(L"Resize back down to the original size");
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount }));
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(historySize + initialVisibleRowCount));
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@@ -254,7 +254,7 @@ void IslandWindow::Initialize()
void IslandWindow::OnSize(const UINT width, const UINT height)
{
// update the interop window size
SetWindowPos(_interopWindowHandle, 0, 0, 0, width, height, SWP_SHOWWINDOW);
SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW);
if (_rootGrid)
{

View File

@@ -5,7 +5,6 @@
********************************************************/
#include "pch.h"
#include "NonClientIslandWindow.h"
#include "../types/inc/ThemeUtils.h"
#include "../types/inc/utils.hpp"
#include "TerminalThemeHelpers.h"
@@ -568,9 +567,6 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept
return _OnNcHitTest({ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) });
case WM_PAINT:
return _OnPaint();
case WM_ACTIVATE:
// If we do this every time we're activated, it should be close enough to correct.
TerminalTrySetDarkTheme(_window.get());
}
return IslandWindow::MessageHandler(message, wParam, lParam);
@@ -639,7 +635,7 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept
}
::FillRect(opaqueDc, &rcRest, _backgroundBrush.get());
::BufferedPaintSetAlpha(buf, NULL, 255);
::BufferedPaintSetAlpha(buf, nullptr, 255);
::EndBufferedPaint(buf, TRUE);
}
@@ -688,7 +684,7 @@ void NonClientIslandWindow::_UpdateFrameTheme() const
break;
}
LOG_IF_FAILED(ThemeUtils::SetWindowFrameDarkMode(_window.get(), isDarkMode));
LOG_IF_FAILED(TerminalTrySetDarkTheme(_window.get(), isDarkMode));
}
// Method Description:

View File

@@ -112,7 +112,7 @@
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Terminal.ThemeHelpers.0.1.200305005\build\native\Terminal.ThemeHelpers.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Terminal.ThemeHelpers.0.1.200305005\build\native\Terminal.ThemeHelpers.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets'))" />
</Target>
<!-- Override GetPackagingOutputs to roll up all our dependencies.
@@ -143,5 +143,5 @@
</Target>
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
<Import Project="..\..\..\packages\Terminal.ThemeHelpers.0.1.200305005\build\native\Terminal.ThemeHelpers.targets" Condition="Exists('..\..\..\packages\Terminal.ThemeHelpers.0.1.200305005\build\native\Terminal.ThemeHelpers.targets')" />
<Import Project="..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets" Condition="Exists('..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets')" />
</Project>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.3.191217003-prerelease" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.1-rc" targetFramework="native" />
<package id="Terminal.ThemeHelpers" version="0.1.200305005" targetFramework="native" />
<package id="Terminal.ThemeHelpers" version="0.2.200324001" targetFramework="native" />
</packages>

View File

@@ -2,7 +2,7 @@
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
@@ -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\chromium;$(SolutionDir)\dep\dynamic_bitset;$(SolutionDir)\dep\libpopcnt;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;$(OpenConsoleDir)\dep\wil\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile>
<!-- Manually include the generated TerminalCore header's path, because
@@ -147,6 +147,9 @@
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
<ProjectReference Include="..\TerminalApp\TerminalApp.vcxproj">
<Project>{ca5cad1a-44bd-4ac7-ac72-f16e576fdd12}</Project>
</ProjectReference>
@@ -156,16 +159,18 @@
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets')" />
<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.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.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')" />
</ImportGroup>
<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\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target>
</Project>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@@ -5,16 +5,20 @@
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/Profile.h"
#include "../TerminalApp/CascadiaSettings.h"
#include "../LocalTests_TerminalApp/JsonTestClass.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings;
namespace TerminalAppUnitTests
{
class JsonTests
class JsonTests : public JsonTestClass
{
BEGIN_TEST_CLASS(JsonTests)
TEST_CLASS_PROPERTY(L"ActivationContext", L"TerminalApp.Unit.Tests.manifest")
@@ -26,10 +30,11 @@ namespace TerminalAppUnitTests
TEST_METHOD(DiffProfile);
TEST_METHOD(DiffProfileWithNull);
TEST_METHOD(TestWrongValueType);
TEST_CLASS_SETUP(ClassSetup)
{
reader = std::unique_ptr<Json::CharReader>(Json::CharReaderBuilder::CharReaderBuilder().newCharReader());
InitializeJsonReader();
// Use 4 spaces to indent instead of \t
_builder.settings_["indentation"] = " ";
return true;
@@ -39,7 +44,6 @@ namespace TerminalAppUnitTests
void VerifyParseFailed(std::string content);
private:
std::unique_ptr<Json::CharReader> reader;
Json::StreamWriterBuilder _builder;
};
@@ -47,7 +51,7 @@ namespace TerminalAppUnitTests
{
Json::Value root;
std::string errs;
const bool parseResult = reader->parse(content.c_str(), content.c_str() + content.size(), &root, &errs);
const bool parseResult = _reader->parse(content.c_str(), content.c_str() + content.size(), &root, &errs);
VERIFY_IS_TRUE(parseResult, winrt::to_hstring(errs).c_str());
return root;
}
@@ -55,7 +59,7 @@ namespace TerminalAppUnitTests
{
Json::Value root;
std::string errs;
const bool parseResult = reader->parse(content.c_str(), content.c_str() + content.size(), &root, &errs);
const bool parseResult = _reader->parse(content.c_str(), content.c_str() + content.size(), &root, &errs);
VERIFY_IS_FALSE(parseResult);
}
@@ -79,6 +83,7 @@ namespace TerminalAppUnitTests
"\"brightRed\" : \"#E74856\","
"\"brightWhite\" : \"#F2F2F2\","
"\"brightYellow\" : \"#F9F1A5\","
"\"cursorColor\" : \"#FFFFFF\","
"\"cyan\" : \"#3A96DD\","
"\"foreground\" : \"#F2F2F2\","
"\"green\" : \"#13A10E\","
@@ -96,6 +101,7 @@ namespace TerminalAppUnitTests
VERIFY_ARE_EQUAL(ARGB(0, 0xf2, 0xf2, 0xf2), scheme.GetForeground());
VERIFY_ARE_EQUAL(ARGB(0, 0x0c, 0x0c, 0x0c), scheme.GetBackground());
VERIFY_ARE_EQUAL(ARGB(0, 0x13, 0x13, 0x13), scheme.GetSelectionBackground());
VERIFY_ARE_EQUAL(ARGB(0, 0xFF, 0xFF, 0xFF), scheme.GetCursorColor());
std::array<COLORREF, COLOR_TABLE_SIZE> expectedCampbellTable;
auto campbellSpan = gsl::span<COLORREF>(&expectedCampbellTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
@@ -219,4 +225,57 @@ namespace TerminalAppUnitTests
VERIFY_IS_TRUE("bar" == diff["icon"].asString());
}
void JsonTests::TestWrongValueType()
{
// This json blob has a whole bunch of settings with the wrong value
// types - strings for int values, ints for strings, floats for ints,
// etc. When we encounter data that's the wrong data type, we should
// gracefully ignore it, as opposed to throwing an exception, causing us
// to fail to load the settings at all.
const std::string settings0String{ R"(
{
"defaultProfile" : "{00000000-1111-0000-0000-000000000000}",
"profiles": [
{
"guid" : "{00000000-1111-0000-0000-000000000000}",
"acrylicOpacity" : "0.5",
"closeOnExit" : "true",
"fontSize" : "10",
"historySize" : 1234.5678,
"padding" : 20,
"snapOnInput" : "false",
"icon" : 4,
"backgroundImageOpacity": false,
"useAcrylic" : 14
}
]
})" };
const auto settings0Json = VerifyParseSucceeded(settings0String);
CascadiaSettings settings;
settings._ParseJsonString(settings0String, false);
// We should not throw an exception trying to parse the settings here.
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(1u, settings._profiles.size());
auto& profile = settings._profiles.at(0);
Profile defaults{};
VERIFY_ARE_EQUAL(defaults._acrylicTransparency, profile._acrylicTransparency);
VERIFY_ARE_EQUAL(defaults._closeOnExitMode, profile._closeOnExitMode);
VERIFY_ARE_EQUAL(defaults._fontSize, profile._fontSize);
VERIFY_ARE_EQUAL(defaults._historySize, profile._historySize);
// A 20 as an int can still be treated as a json string
VERIFY_ARE_EQUAL(L"20", profile._padding);
VERIFY_ARE_EQUAL(defaults._snapOnInput, profile._snapOnInput);
// 4 is a valid string value
VERIFY_ARE_EQUAL(L"4", profile._icon);
// false is not a valid optional<double>
VERIFY_IS_FALSE(profile._backgroundImageOpacity.has_value());
VERIFY_ARE_EQUAL(defaults._useAcrylic, profile._useAcrylic);
}
}

View File

@@ -66,6 +66,7 @@
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<LinkIncremental>false</LinkIncremental>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<ItemDefinitionGroup>
@@ -82,7 +83,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\chromium;$(SolutionDir)\dep\dynamic_bitset;$(SolutionDir)\dep\libpopcnt;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<RuntimeTypeInfo>false</RuntimeTypeInfo>

View File

@@ -2,7 +2,7 @@
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>UNIT_TESTING;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>INLINE_TEST_METHOD_MARKUP;UNIT_TESTING;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.51.200127004\build\Taef.Redist.Wlk.targets" Condition="Exists('$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.51.200127004\build\Taef.Redist.Wlk.targets')" />

View File

@@ -52,13 +52,13 @@
</ItemDefinitionGroup>
<ImportGroup Label="ExtensionTargets">
<Import Project="$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<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('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -3,7 +3,7 @@
<Import Project="..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<!-- 17134 is RS4, 17763 is RS5, 18362 is 19H1 -->

View File

@@ -212,7 +212,7 @@ void CursorBlinker::KillCaretTimer()
bRet = DeleteTimerQueueTimer(_hCaretBlinkTimerQueue,
_hCaretBlinkTimer,
NULL);
nullptr);
// According to https://msdn.microsoft.com/en-us/library/windows/desktop/ms682569(v=vs.85).aspx
// A failure to delete the timer with the LastError being ERROR_IO_PENDING means that the timer is

View File

@@ -21,7 +21,7 @@ static bool ConhostV2ForcedInRegistry()
// If the registry value doesn't exist, or exists and is non-zero, we should default to using the v2 console.
// Otherwise, in the case of an explicit value of 0, we should use the legacy console.
bool fShouldUseConhostV2 = true;
PCSTR pszErrorDescription = NULL;
PCSTR pszErrorDescription = nullptr;
bool fIgnoreError = false;
// open HKCU\Console
@@ -110,10 +110,10 @@ static bool ShouldUseLegacyConhost(const ConsoleArguments& args)
// We expect legacy launches to be infrequent enough to not cause an issue.
TraceLoggingWrite(g_ConhostLauncherProvider, "IsLegacyLoaded", TraceLoggingBool(true, "ConsoleLegacy"), TraceLoggingKeyword(MICROSOFT_KEYWORD_TELEMETRY));
PCWSTR pszConhostDllName = L"ConhostV1.dll";
const PCWSTR pszConhostDllName = L"ConhostV1.dll";
// Load our implementation, and then Load/Launch the IO thread.
wil::unique_hmodule hConhostBin(LoadLibraryExW(pszConhostDllName, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32));
wil::unique_hmodule hConhostBin(LoadLibraryExW(pszConhostDllName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
if (hConhostBin.get() != nullptr)
{
typedef NTSTATUS (*PFNCONSOLECREATEIOTHREAD)(__in HANDLE Server);

View File

@@ -153,7 +153,7 @@ void TestGetConsoleAliasHelper(TCH* ptszSourceGiven,
// This is strange because it's a scope exit so we need to declare in the parent scope, then let it go if we didn't actually need it.
// I just prefer keeping the exit next to the allocation so it doesn't get lost.
auto removeAliasOnExit = wil::scope_exit([&] {
AddConsoleAliasT(ptszSource, NULL, ptszExeName);
AddConsoleAliasT(ptszSource, nullptr, ptszExeName);
});
if (!bSetFirst)
{

View File

@@ -219,7 +219,7 @@ void BufferTests::ChafaGifPerformance()
SetConsoleOutputCP(CP_UTF8);
// Taken from: https://blog.kowalczyk.info/article/zy/Embedding-binary-resources-on-Windows.html
HGLOBAL res_handle = NULL;
HGLOBAL res_handle = nullptr;
HRSRC res;
char* res_data;
DWORD res_size;

View File

@@ -61,7 +61,7 @@ class FileTests
END_TEST_METHOD();*/
};
static HANDLE _cancellationEvent = 0;
static HANDLE _cancellationEvent = nullptr;
bool FileTests::ClassSetup()
{

View File

@@ -243,7 +243,7 @@ void InputTests::TestPeekConsoleInvalid()
void InputTests::TestReadConsoleInvalid()
{
DWORD nRead = (DWORD)-1;
VERIFY_WIN32_BOOL_FAILED(ReadConsoleInput(0, nullptr, 0, &nRead));
VERIFY_WIN32_BOOL_FAILED(ReadConsoleInput(nullptr, nullptr, 0, &nRead));
VERIFY_ARE_EQUAL(nRead, (DWORD)0);
nRead = (DWORD)-1;
@@ -275,7 +275,7 @@ void InputTests::TestReadConsoleInvalid()
void InputTests::TestWriteConsoleInvalid()
{
DWORD nWrite = (DWORD)-1;
VERIFY_WIN32_BOOL_FAILED(WriteConsoleInput(0, nullptr, 0, &nWrite));
VERIFY_WIN32_BOOL_FAILED(WriteConsoleInput(nullptr, nullptr, 0, &nWrite));
VERIFY_ARE_EQUAL(nWrite, (DWORD)0);
// weird: WriteConsoleInput with INVALID_HANDLE_VALUE writes garbage to lpNumberOfEventsWritten, whereas
@@ -358,7 +358,7 @@ void InputTests::TestReadConsolePasswordScenario()
{
wchar_t ch;
DWORD c;
int err = ReadConsoleW(hIn, &ch, 1, &c, 0);
int err = ReadConsoleW(hIn, &ch, 1, &c, nullptr);
if (!err || c != 1)
{

View File

@@ -186,9 +186,9 @@ bool Common::TestBufferSetup()
_hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
0 /*dwShareMode*/,
NULL /*lpSecurityAttributes*/,
nullptr /*lpSecurityAttributes*/,
CONSOLE_TEXTMODE_BUFFER,
NULL /*lpReserved*/);
nullptr /*lpReserved*/);
VERIFY_ARE_NOT_EQUAL(_hConsole, INVALID_HANDLE_VALUE, L"Creating our test screen buffer.");

View File

@@ -250,7 +250,7 @@ void CommandHistory::Empty()
{
_commands.clear();
LastDisplayed = -1;
Flags = CLE_RESET;
WI_SetFlag(Flags, CLE_RESET);
}
bool CommandHistory::AtFirstCommand() const

View File

@@ -13,7 +13,7 @@
CLIENT_ID ClientId;
ClientId.UniqueProcess = UlongToHandle(*ProcessId);
ClientId.UniqueThread = 0;
ClientId.UniqueThread = nullptr;
HANDLE ProcessHandle;
NTSTATUS Status = s_NtOpenProcess(&ProcessHandle, PROCESS_QUERY_LIMITED_INFORMATION, &oa, &ClientId);

View File

@@ -123,7 +123,12 @@ COORD RenderData::GetCursorPosition() const noexcept
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
return cursor.GetPosition();
COORD effectiveCursor = cursor.GetPosition();
if (cursor.IsDelayedEOLWrap() && cursor.GetDelayedAtPosition() == effectiveCursor)
{
effectiveCursor.X++;
}
return effectiveCursor;
}
// Method Description:
@@ -359,6 +364,11 @@ const bool RenderData::IsSelectionActive() const
return Selection::Instance().IsAreaSelected();
}
const bool RenderData::IsBlockSelection() const noexcept
{
return !Selection::Instance().IsLineSelection();
}
// Routine Description:
// - If a selection exists, clears it and restores the state.
// Will also unblock a blocked write if one exists.

View File

@@ -60,6 +60,7 @@ public:
#pragma region IUiaData
const bool IsSelectionActive() const override;
const bool IsBlockSelection() const noexcept override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const noexcept;

View File

@@ -269,7 +269,7 @@ void ConsoleCheckDebug()
ServerInformation.InputAvailableEvent = ServiceLocator::LocateGlobals().hInputEvent.get();
RETURN_IF_FAILED(g.pDeviceComm->SetServerInformation(&ServerInformation));
HANDLE const hThread = CreateThread(nullptr, 0, ConsoleIoThread, 0, 0, nullptr);
HANDLE const hThread = CreateThread(nullptr, 0, ConsoleIoThread, nullptr, 0, nullptr);
RETURN_HR_IF(E_HANDLE, hThread == nullptr);
LOG_IF_WIN32_BOOL_FALSE(CloseHandle(hThread)); // The thread will run on its own and close itself. Free the associated handle.

View File

@@ -44,7 +44,7 @@ class CommandLineTests
m_state->PrepareGlobalInputBuffer();
m_state->PrepareReadHandle();
m_state->PrepareCookedReadData();
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", (HANDLE)0);
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
if (!m_pHistory)
{
return false;
@@ -54,7 +54,7 @@ class CommandLineTests
TEST_METHOD_CLEANUP(MethodCleanup)
{
CommandHistory::s_Free((HANDLE)0);
CommandHistory::s_Free(nullptr);
m_pHistory = nullptr;
m_state->CleanupCookedReadData();
m_state->CleanupReadHandle();

View File

@@ -50,7 +50,7 @@ class CommandListPopupTests
m_state->PrepareGlobalInputBuffer();
m_state->PrepareReadHandle();
m_state->PrepareCookedReadData();
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", (HANDLE)0);
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
// resize command history storage to 50 items so that we don't cycle on accident
// when PopupTestHelper::InitLongHistory() is called.
CommandHistory::s_ResizeAll(50);
@@ -63,7 +63,7 @@ class CommandListPopupTests
TEST_METHOD_CLEANUP(MethodCleanup)
{
CommandHistory::s_Free((HANDLE)0);
CommandHistory::s_Free(nullptr);
m_pHistory = nullptr;
m_state->CleanupCookedReadData();
m_state->CleanupReadHandle();

View File

@@ -46,7 +46,7 @@ class CommandNumberPopupTests
m_state->PrepareGlobalInputBuffer();
m_state->PrepareReadHandle();
m_state->PrepareCookedReadData();
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", (HANDLE)0);
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
if (!m_pHistory)
{
return false;
@@ -56,7 +56,7 @@ class CommandNumberPopupTests
TEST_METHOD_CLEANUP(MethodCleanup)
{
CommandHistory::s_Free((HANDLE)0);
CommandHistory::s_Free(nullptr);
m_pHistory = nullptr;
m_state->CleanupCookedReadData();
m_state->CleanupReadHandle();

View File

@@ -113,6 +113,7 @@ class ConptyOutputTests
TEST_METHOD(SimpleWriteOutputTest);
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);
TEST_METHOD(InvalidateUntilOneBeforeEnd);
private:
bool _writeCallback(const char* const pch, size_t const cch);
@@ -124,10 +125,14 @@ private:
bool ConptyOutputTests::_writeCallback(const char* const pch, size_t const cch)
{
// Since rendering happens on a background thread that doesn't have the exception handler on it
// we need to rely on VERIFY's return codes instead of exceptions.
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
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()));
RETURN_BOOL_IF_FALSE(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();
@@ -135,8 +140,8 @@ bool ConptyOutputTests::_writeCallback(const char* const pch, size_t const cch)
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_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first.length(), cch));
RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first, actualString));
return true;
}
@@ -301,16 +306,65 @@ void ConptyOutputTests::WriteAFewSimpleLines()
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");
// Jump down to the fourth line because emitting spaces didn't do anything
// and we will skip to emitting the CCC segment.
expectedOutput.push_back("\x1b[4;1H");
expectedOutput.push_back("CCC");
// Cursor goes back on.
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::InvalidateUntilOneBeforeEnd()
{
Log::Comment(NoThrowString().Format(
L"Make sure we don't use EL and wipe out the last column of text"));
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();
// Move the cursor to width-15, draw 15 characters
sm.ProcessString(L"\x1b[1;66H");
sm.ProcessString(L"ABCDEFGHIJKLMNO");
{
auto iter = tb.GetCellDataAt({ 78, 0 });
VERIFY_ARE_EQUAL(L"N", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
}
expectedOutput.push_back("\x1b[65C");
expectedOutput.push_back("ABCDEFGHIJKLMNO");
expectedOutput.push_back("\x1b[1;80H"); // we move the cursor to the end of the line after paint
VERIFY_SUCCEEDED(renderer.PaintFrame());
// overstrike the first with X and the middle 8 with spaces
sm.ProcessString(L"\x1b[1;66H");
// ABCDEFGHIJKLMNO
sm.ProcessString(L"X ");
{
auto iter = tb.GetCellDataAt({ 78, 0 });
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
}
expectedOutput.push_back("\x1b[1;66H");
expectedOutput.push_back("X"); // sequence optimizer should choose ECH here
expectedOutput.push_back("\x1b[13X");
expectedOutput.push_back("\x1b[13C");
expectedOutput.push_back("\x1b[?25h"); // we turn the cursor back on for good measure
VERIFY_SUCCEEDED(renderer.PaintFrame());
}

View File

@@ -689,7 +689,7 @@ void ConsoleArgumentsTests::IsVtHandleValidTests()
// We use both 0 and INVALID_HANDLE_VALUE as invalid handles since we're not sure
// exactly what will get passed in on the STDIN/STDOUT handles as it can vary wildly
// depending on who is passing it.
VERIFY_IS_FALSE(IsValidHandle(0), L"Zero handle invalid.");
VERIFY_IS_FALSE(IsValidHandle(nullptr), L"Zero handle invalid.");
VERIFY_IS_FALSE(IsValidHandle(INVALID_HANDLE_VALUE), L"Invalid handle invalid.");
VERIFY_IS_TRUE(IsValidHandle(UlongToHandle(0x4)), L"0x4 is valid.");
}

View File

@@ -45,7 +45,7 @@ class CopyToCharPopupTests
m_state->PrepareGlobalInputBuffer();
m_state->PrepareReadHandle();
m_state->PrepareCookedReadData();
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", (HANDLE)0);
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
if (!m_pHistory)
{
return false;
@@ -55,7 +55,7 @@ class CopyToCharPopupTests
TEST_METHOD_CLEANUP(MethodCleanup)
{
CommandHistory::s_Free((HANDLE)0);
CommandHistory::s_Free(nullptr);
m_pHistory = nullptr;
m_state->CleanupCookedReadData();
m_state->CleanupReadHandle();

View File

@@ -148,6 +148,7 @@ class TextBufferTests
void WriteLinesToBuffer(const std::vector<std::wstring>& text, TextBuffer& buffer);
TEST_METHOD(GetWordBoundaries);
TEST_METHOD(GetGlyphBoundaries);
TEST_METHOD(GetTextRects);
TEST_METHOD(GetText);
@@ -2153,6 +2154,59 @@ void TextBufferTests::GetWordBoundaries()
}
}
void TextBufferTests::GetGlyphBoundaries()
{
struct ExpectedResult
{
std::wstring name;
til::point start;
til::point wideGlyphEnd;
til::point normalEnd;
};
// clang-format off
const std::vector<ExpectedResult> expected = {
{ L"Buffer Start", { 0, 0 }, { 2, 0 }, { 1, 0 } },
{ L"Line Start", { 0, 1 }, { 2, 1 }, { 1, 1 } },
{ L"General Case", { 1, 1 }, { 3, 1 }, { 2, 1 } },
{ L"Line End", { 9, 1 }, { 0, 2 }, { 0, 2 } },
{ L"Buffer End", { 9, 9 }, { 0, 10 }, { 0, 10 } },
};
// clang-format on
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:wideGlyph", L"{false, true}")
END_TEST_METHOD_PROPERTIES();
bool wideGlyph;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"wideGlyph", wideGlyph), L"Get wide glyph variant");
COORD bufferSize{ 10, 10 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
// This is the burrito emoji: 🌯
// It's encoded in UTF-16, as needed by the buffer.
const auto burrito = L"\xD83C\xDF2F";
const wchar_t* const output = wideGlyph ? burrito : L"X";
const OutputCellIterator iter{ output };
for (const auto& test : expected)
{
Log::Comment(test.name.c_str());
auto target = test.start;
_buffer->Write(iter, target);
auto start = _buffer->GetGlyphStart(target);
auto end = _buffer->GetGlyphEnd(target);
VERIFY_ARE_EQUAL(test.start, start);
VERIFY_ARE_EQUAL(wideGlyph ? test.wideGlyphEnd : test.normalEnd, end);
}
}
void TextBufferTests::GetTextRects()
{
// GetTextRects() is used to...

View File

@@ -32,7 +32,7 @@ class UtilsTests
m_state->PrepareGlobalFont();
m_state->PrepareGlobalScreenBuffer();
UINT const seed = (UINT)time(NULL);
UINT const seed = (UINT)time(nullptr);
Log::Comment(String().Format(L"Setting random seed to : %d", seed));
srand(seed);

View File

@@ -257,21 +257,22 @@ void VtRendererTest::Xterm256TestInvalidate()
VERIFY_IS_FALSE(engine->_firstPaint);
});
Viewport view = SetUpViewport();
const Viewport view = SetUpViewport();
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating all invalidates the whole viewport."));
VERIFY_SUCCEEDED(engine->InvalidateAll());
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
});
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating anything only invalidates that portion"));
SMALL_RECT invalid = { 1, 1, 1, 1 };
SMALL_RECT invalid = { 1, 1, 2, 2 };
VERIFY_SUCCEEDED(engine->Invalidate(&invalid));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
VERIFY_IS_TRUE(engine->_invalidMap.one());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, *(engine->_invalidMap.begin()));
});
Log::Comment(NoThrowString().Format(
@@ -284,7 +285,9 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[H"); // Go Home
qExpectedInput.push_back("\x1b[L"); // insert a line
@@ -300,7 +303,17 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but the cursor is already at the home position
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@@ -314,7 +327,9 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[32;1H"); // Bottom of buffer
qExpectedInput.push_back("\n"); // Scroll down once
@@ -329,7 +344,17 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but we're already at the bottom from the last call.
qExpectedInput.push_back("\n\n\n"); // Scroll down three times
@@ -349,7 +374,18 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
qExpectedInput.push_back("\x1b[H"); // Go to home
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@@ -357,21 +393,39 @@ void VtRendererTest::Xterm256TestInvalidate()
scrollDelta = { 0, 1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
scrollDelta = { 0, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
qExpectedInput.push_back("\x1b[2J");
TestPaint(*engine, [&]() {
Log::Comment(NoThrowString().Format(
L"---- Scrolled one down and one up, nothing should change ----"
L" But it still does for now MSFT:14169294"));
invalid = view.ToExclusive();
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// only the bottom line should be dirty.
// When we scrolled down, the bitmap looked like this:
// 1111
// 0000
// 0000
// 0000
// And then we scrolled up and the top line fell off and a bottom
// line was filled in like this:
// 0000
// 0000
// 0000
// 1111
const til::rectangle expected{ til::point{ view.Left(), view.BottomInclusive() }, til::size{ view.Width(), 1 } };
VERIFY_ARE_EQUAL(expected, invalidRect);
VERIFY_SUCCEEDED(engine->ScrollFrame());
});
@@ -730,15 +784,16 @@ void VtRendererTest::XtermTestInvalidate()
L"Make sure that invalidating all invalidates the whole viewport."));
VERIFY_SUCCEEDED(engine->InvalidateAll());
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
});
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating anything only invalidates that portion"));
SMALL_RECT invalid = { 1, 1, 1, 1 };
SMALL_RECT invalid = { 1, 1, 2, 2 };
VERIFY_SUCCEEDED(engine->Invalidate(&invalid));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
VERIFY_IS_TRUE(engine->_invalidMap.one());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, *(engine->_invalidMap.begin()));
});
Log::Comment(NoThrowString().Format(
@@ -751,7 +806,9 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[H"); // Go Home
qExpectedInput.push_back("\x1b[L"); // insert a line
@@ -766,7 +823,17 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but the cursor is already at the home position
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@@ -780,7 +847,9 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[32;1H"); // Bottom of buffer
qExpectedInput.push_back("\n"); // Scroll down once
@@ -795,7 +864,17 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but we're already at the bottom from the last call.
qExpectedInput.push_back("\n\n\n"); // Scroll down three times
@@ -815,7 +894,18 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
qExpectedInput.push_back("\x1b[H"); // Go to home
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@@ -823,21 +913,39 @@ void VtRendererTest::XtermTestInvalidate()
scrollDelta = { 0, 1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
scrollDelta = { 0, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
qExpectedInput.push_back("\x1b[2J");
TestPaint(*engine, [&]() {
Log::Comment(NoThrowString().Format(
L"---- Scrolled one down and one up, nothing should change ----"
L" But it still does for now MSFT:14169294"));
invalid = view.ToExclusive();
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
const auto runs = engine->_invalidMap.runs();
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// only the bottom line should be dirty.
// When we scrolled down, the bitmap looked like this:
// 1111
// 0000
// 0000
// 0000
// And then we scrolled up and the top line fell off and a bottom
// line was filled in like this:
// 0000
// 0000
// 0000
// 1111
const til::rectangle expected{ til::point{ view.Left(), view.BottomInclusive() }, til::size{ view.Width(), 1 } };
VERIFY_ARE_EQUAL(expected, invalidRect);
VERIFY_SUCCEEDED(engine->ScrollFrame());
});
@@ -1051,15 +1159,15 @@ void VtRendererTest::WinTelnetTestInvalidate()
L"Make sure that invalidating all invalidates the whole viewport."));
VERIFY_SUCCEEDED(engine->InvalidateAll());
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
});
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating anything only invalidates that portion"));
SMALL_RECT invalid = { 1, 1, 1, 1 };
SMALL_RECT invalid = { 1, 1, 2, 2 };
VERIFY_SUCCEEDED(engine->Invalidate(&invalid));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, *(engine->_invalidMap.begin()));
});
Log::Comment(NoThrowString().Format(
@@ -1067,7 +1175,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
COORD scrollDelta = { 0, 1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); // sentinel
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@@ -1076,7 +1184,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { 0, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@@ -1085,7 +1193,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { 1, 0 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@@ -1094,7 +1202,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { -1, 0 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@@ -1103,7 +1211,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { 1, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@@ -1351,7 +1459,7 @@ void VtRendererTest::TestResize()
VERIFY_SUCCEEDED(engine->UpdateViewport(newView.ToInclusive()));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(newView, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
VERIFY_IS_FALSE(engine->_firstPaint);
VERIFY_IS_FALSE(engine->_suppressResizeRepaint);
});

View File

@@ -79,12 +79,25 @@
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
#include <intsafe.h>
// LibPopCnt - Fast C/C++ bit population count library (on bits in an array)
#include <libpopcnt.h>
// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting)
// Variable-size compressed-storage header-only bit flag storage library.
#include <dynamic_bitset.hpp>
// SAL
#include <sal.h>
// WRL
#include <wrl.h>
// WEX/TAEF testing
// Include before TIL if we're unit testing so it can light up WEX/TAEF template extensions
#ifdef UNIT_TESTING
#include <WexTestClass.h>
#endif
// TIL - Terminal Implementation Library
#ifndef BLOCK_TIL // Certain projects may want to include TIL manually to gain superpowers
#include "til.h"

View File

@@ -3,12 +3,16 @@
#pragma once
#define _TIL_INLINEPREFIX __declspec(noinline) inline
#include "til/at.h"
#include "til/color.h"
#include "til/some.h"
#include "til/size.h"
#include "til/point.h"
#include "til/rectangle.h"
#include "til/operators.h"
#include "til/bitmap.h"
#include "til/u8u16convert.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"

448
src/inc/til/bitmap.h Normal file
View File

@@ -0,0 +1,448 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#ifdef UNIT_TESTING
class BitmapTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
namespace details
{
class _bitmap_const_iterator
{
public:
using iterator_category = typename std::input_iterator_tag;
using value_type = typename const til::rectangle;
using difference_type = typename ptrdiff_t;
using pointer = typename const til::rectangle*;
using reference = typename const til::rectangle&;
_bitmap_const_iterator(const dynamic_bitset<>& values, til::rectangle rc, ptrdiff_t pos) :
_values(values),
_rc(rc),
_pos(pos),
_end(rc.size().area())
{
_calculateArea();
}
_bitmap_const_iterator& operator++()
{
_pos = _nextPos;
_calculateArea();
return (*this);
}
_bitmap_const_iterator operator++(int)
{
const auto prev = *this;
++*this;
return prev;
}
constexpr bool operator==(const _bitmap_const_iterator& other) const noexcept
{
return _pos == other._pos && _values == other._values;
}
constexpr bool operator!=(const _bitmap_const_iterator& other) const noexcept
{
return !(*this == other);
}
constexpr bool operator<(const _bitmap_const_iterator& other) const noexcept
{
return _pos < other._pos;
}
constexpr bool operator>(const _bitmap_const_iterator& other) const noexcept
{
return _pos > other._pos;
}
constexpr reference operator*() const noexcept
{
return _run;
}
constexpr pointer operator->() const noexcept
{
return &_run;
}
private:
const dynamic_bitset<>& _values;
const til::rectangle _rc;
ptrdiff_t _pos;
ptrdiff_t _nextPos;
const ptrdiff_t _end;
til::rectangle _run;
void _calculateArea()
{
// Backup the position as the next one.
_nextPos = _pos;
// Seek forward until we find an on bit.
while (_nextPos < _end && !_values[_nextPos])
{
++_nextPos;
}
// If we haven't reached the end yet...
if (_nextPos < _end)
{
// pos is now at the first on bit.
const auto runStart = _rc.point_at(_nextPos);
// We'll only count up until the end of this row.
// a run can be a max of one row tall.
const ptrdiff_t rowEndIndex = _rc.index_of(til::point(_rc.right() - 1, runStart.y())) + 1;
// Find the length for the rectangle.
ptrdiff_t runLength = 0;
// We have at least 1 so start with a do/while.
do
{
++_nextPos;
++runLength;
} while (_nextPos < rowEndIndex && _values[_nextPos]);
// Keep going until we reach end of row, end of the buffer, or the next bit is off.
// Assemble and store that run.
_run = til::rectangle{ runStart, til::size{ runLength, static_cast<ptrdiff_t>(1) } };
}
else
{
// If we reached the end, set the pos because the run is empty.
_pos = _nextPos;
_run = til::rectangle{};
}
}
};
}
class bitmap
{
public:
using const_iterator = details::_bitmap_const_iterator;
bitmap() noexcept :
_sz{},
_rc{},
_bits{},
_dirty{},
_runs{}
{
}
bitmap(til::size sz) :
bitmap(sz, false)
{
}
bitmap(til::size sz, bool fill) :
_sz(sz),
_rc(sz),
_bits(_sz.area()),
_dirty(fill ? sz : til::rectangle{}),
_runs{}
{
if (fill)
{
set_all();
}
}
constexpr bool operator==(const bitmap& other) const noexcept
{
return _sz == other._sz &&
_rc == other._rc &&
_dirty == other._dirty && // dirty is before bits because it's a rough estimate of bits and a faster comparison.
_bits == other._bits;
// _runs excluded because it's a cache of generated state.
}
constexpr bool operator!=(const bitmap& other) const noexcept
{
return !(*this == other);
}
const_iterator begin() const
{
return const_iterator(_bits, _sz, 0);
}
const_iterator end() const
{
return const_iterator(_bits, _sz, _sz.area());
}
const std::vector<til::rectangle>& runs() const
{
// If we don't have cached runs, rebuild.
if (!_runs.has_value())
{
// If there's only one square dirty, quick save it off and be done.
if (one())
{
_runs.emplace({ _dirty });
}
else
{
_runs.emplace(begin(), end());
}
}
// Return a reference to the runs.
return _runs.value();
}
// optional fill the uncovered area with bits.
void translate(const til::point delta, bool fill = false)
{
// FUTURE: PERF: GH #4015: This could use in-place walk semantics instead of a temporary.
til::bitmap other{ _sz };
for (auto run : *this)
{
// Offset by the delta
run += delta;
// Intersect with the bounds of our bitmap area
// as part of it could have slid out of bounds.
run &= _rc;
// Set it into the new bitmap.
other.set(run);
}
// If we were asked to fill... find the uncovered region.
if (fill)
{
// Original Rect of As.
//
// X <-- origin
// A A A A
// A A A A
// A A A A
// A A A A
const auto originalRect = _rc;
// If Delta = (2, 2)
// Translated Rect of Bs.
//
// X <-- origin
//
//
// B B B B
// B B B B
// B B B B
// B B B B
const auto translatedRect = _rc + delta;
// Subtract the B from the A one to see what wasn't filled by the move.
// C is the overlap of A and B:
//
// X <-- origin
// A A A A 1 1 1 1
// A A A A 1 1 1 1
// A A C C B B subtract 2 2
// A A C C B B ---------> 2 2
// B B B B A - B
// B B B B
//
// 1 and 2 are the spaces to fill that are "uncovered".
const auto fillRects = originalRect - translatedRect;
for (const auto& f : fillRects)
{
other.set(f);
}
}
// Swap us with the temporary one.
std::swap(other, *this);
}
void set(const til::point pt)
{
THROW_HR_IF(E_INVALIDARG, !_rc.contains(pt));
_runs.reset(); // reset cached runs on any non-const method
til::at(_bits, _rc.index_of(pt)) = true;
_dirty |= til::rectangle{ pt };
}
void set(const til::rectangle rc)
{
THROW_HR_IF(E_INVALIDARG, !_rc.contains(rc));
_runs.reset(); // reset cached runs on any non-const method
for (const auto pt : rc)
{
til::at(_bits, _rc.index_of(pt)) = true;
}
_dirty |= rc;
}
void set_all() noexcept
{
_runs.reset(); // reset cached runs on any non-const method
_bits.set();
_dirty = _rc;
}
void reset_all() noexcept
{
_runs.reset(); // reset cached runs on any non-const method
_bits.reset();
_dirty = {};
}
// True if we resized. False if it was the same size as before.
// Set fill if you want the new region (on growing) to be marked dirty.
bool resize(til::size size, bool fill = false)
{
_runs.reset(); // reset cached runs on any non-const method
// Don't resize if it's not different
if (_sz != size)
{
// Make a new bitmap for the other side, empty initially.
auto newMap = bitmap(size, false);
// Copy any regions that overlap from this map to the new one.
// Just iterate our runs...
for (const auto run : *this)
{
// intersect them with the new map
// so we don't attempt to set bits that fit outside
// the new one.
const auto intersect = run & newMap._rc;
// and if there is still anything left, set them.
if (!intersect.empty())
{
newMap.set(intersect);
}
}
// Then, if we were requested to fill the new space on growing,
// find the space in the new rectangle that wasn't in the old
// and fill it up.
if (fill)
{
// A subtraction will yield anything in the new that isn't
// a part of the old.
const auto newAreas = newMap._rc - _rc;
for (const auto& area : newAreas)
{
newMap.set(area);
}
}
// Swap and return.
std::swap(newMap, *this);
return true;
}
else
{
return false;
}
}
bool one() const
{
return _dirty.size() == til::size{ 1, 1 };
}
constexpr bool any() const noexcept
{
return !none();
}
constexpr bool none() const noexcept
{
return _dirty.empty();
}
constexpr bool all() const noexcept
{
return _dirty == _rc;
}
std::wstring to_string() const
{
std::wstringstream wss;
wss << std::endl
<< L"Bitmap of size " << _sz.to_string() << " contains the following dirty regions:" << std::endl;
wss << L"Runs:" << std::endl;
for (auto& item : *this)
{
wss << L"\t- " << item.to_string() << std::endl;
}
return wss.str();
}
private:
til::rectangle _dirty;
til::size _sz;
til::rectangle _rc;
dynamic_bitset<> _bits;
mutable std::optional<std::vector<til::rectangle>> _runs;
#ifdef UNIT_TESTING
friend class ::BitmapTests;
#endif
};
}
#ifdef __WEX_COMMON_H__
namespace WEX::TestExecution
{
template<>
class VerifyOutputTraits<::til::bitmap>
{
public:
static WEX::Common::NoThrowString ToString(const ::til::bitmap& rect)
{
return WEX::Common::NoThrowString(rect.to_string().c_str());
}
};
template<>
class VerifyCompareTraits<::til::bitmap, ::til::bitmap>
{
public:
static bool AreEqual(const ::til::bitmap& expected, const ::til::bitmap& actual) noexcept
{
return expected == actual;
}
static bool AreSame(const ::til::bitmap& expected, const ::til::bitmap& actual) noexcept
{
return &expected == &actual;
}
static bool IsLessThan(const ::til::bitmap& expectedLess, const ::til::bitmap& expectedGreater) = delete;
static bool IsGreaterThan(const ::til::bitmap& expectedGreater, const ::til::bitmap& expectedLess) = delete;
static bool IsNull(const ::til::bitmap& object) noexcept
{
return object == til::bitmap{};
}
};
};
#endif

59
src/inc/til/operators.h Normal file
View File

@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "rectangle.h"
#include "size.h"
#include "bitmap.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
// Operators go here when they involve two headers that can't/don't include each other.
#pragma region POINT VS SIZE
// This is a convenience and will take X vs WIDTH and Y vs HEIGHT.
_TIL_INLINEPREFIX point operator+(const point& lhs, const size& rhs)
{
return lhs + til::point{ rhs.width(), rhs.height() };
}
_TIL_INLINEPREFIX point operator-(const point& lhs, const size& rhs)
{
return lhs - til::point{ rhs.width(), rhs.height() };
}
_TIL_INLINEPREFIX point operator*(const point& lhs, const size& rhs)
{
return lhs * til::point{ rhs.width(), rhs.height() };
}
_TIL_INLINEPREFIX point operator/(const point& lhs, const size& rhs)
{
return lhs / til::point{ rhs.width(), rhs.height() };
}
#pragma endregion
#pragma region SIZE VS POINT
// This is a convenience and will take WIDTH vs X and HEIGHT vs Y.
_TIL_INLINEPREFIX size operator+(const size& lhs, const point& rhs)
{
return lhs + til::size(rhs.x(), rhs.y());
}
_TIL_INLINEPREFIX size operator-(const size& lhs, const point& rhs)
{
return lhs - til::size(rhs.x(), rhs.y());
}
_TIL_INLINEPREFIX size operator*(const size& lhs, const point& rhs)
{
return lhs * til::size(rhs.x(), rhs.y());
}
_TIL_INLINEPREFIX size operator/(const size& lhs, const point& rhs)
{
return lhs / til::size(rhs.x(), rhs.y());
}
#pragma endregion
}

View File

@@ -64,11 +64,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !(*this == other);
}
operator bool() const noexcept
{
return _x != 0 || _y != 0;
}
constexpr bool operator<(const point& other) const noexcept
{
if (_y < other._y)
@@ -198,6 +193,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#endif
std::wstring to_string() const
{
return wil::str_printf<std::wstring>(L"(X:%td, Y:%td)", x(), y());
}
protected:
ptrdiff_t _x;
ptrdiff_t _y;
@@ -217,7 +217,7 @@ namespace WEX::TestExecution
public:
static WEX::Common::NoThrowString ToString(const ::til::point& point)
{
return WEX::Common::NoThrowString().Format(L"(X:%td, Y:%td)", point.x(), point.y());
return WEX::Common::NoThrowString(point.to_string().c_str());
}
};

View File

@@ -13,9 +13,90 @@ class RectangleTests;
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
namespace details
{
class _rectangle_const_iterator
{
public:
constexpr _rectangle_const_iterator(point topLeft, point bottomRight) :
_topLeft(topLeft),
_bottomRight(bottomRight),
_current(topLeft)
{
}
constexpr _rectangle_const_iterator(point topLeft, point bottomRight, point start) :
_topLeft(topLeft),
_bottomRight(bottomRight),
_current(start)
{
}
_rectangle_const_iterator& operator++()
{
ptrdiff_t nextX;
THROW_HR_IF(E_ABORT, !::base::CheckAdd(_current.x(), 1).AssignIfValid(&nextX));
if (nextX >= _bottomRight.x())
{
ptrdiff_t nextY;
THROW_HR_IF(E_ABORT, !::base::CheckAdd(_current.y(), 1).AssignIfValid(&nextY));
// Note for the standard Left-to-Right, Top-to-Bottom walk,
// the end position is one cell below the bottom left.
// (or more accurately, on the exclusive bottom line in the inclusive left column.)
_current = { _topLeft.x(), nextY };
}
else
{
_current = { nextX, _current.y() };
}
return (*this);
}
constexpr bool operator==(const _rectangle_const_iterator& other) const
{
return _current == other._current &&
_topLeft == other._topLeft &&
_bottomRight == other._bottomRight;
}
constexpr bool operator!=(const _rectangle_const_iterator& other) const
{
return !(*this == other);
}
constexpr bool operator<(const _rectangle_const_iterator& other) const
{
return _current < other._current;
}
constexpr bool operator>(const _rectangle_const_iterator& other) const
{
return _current > other._current;
}
constexpr point operator*() const
{
return _current;
}
protected:
point _current;
const point _topLeft;
const point _bottomRight;
#ifdef UNIT_TESTING
friend class ::RectangleTests;
#endif
};
}
class rectangle
{
public:
using const_iterator = details::_rectangle_const_iterator;
constexpr rectangle() noexcept :
rectangle(til::point{ 0, 0 }, til::point{ 0, 0 })
{
@@ -109,12 +190,29 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !(*this == other);
}
constexpr operator bool() const noexcept
explicit constexpr operator bool() const noexcept
{
return _topLeft.x() < _bottomRight.x() &&
_topLeft.y() < _bottomRight.y();
}
constexpr const_iterator begin() const
{
return const_iterator(_topLeft, _bottomRight);
}
constexpr const_iterator end() const
{
// For the standard walk: Left-To-Right then Top-To-Bottom
// the end box is one cell below the left most column.
// |----| 5x2 square. Remember bottom & right are exclusive
// | | while top & left are inclusive.
// X----- X is the end position.
return const_iterator(_topLeft, _bottomRight, { _topLeft.x(), _bottomRight.y() });
}
#pragma region RECTANGLE OPERATORS
// OR = union
constexpr rectangle operator|(const rectangle& other) const noexcept
{
@@ -147,6 +245,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return rectangle{ l, t, r, b };
}
constexpr rectangle& operator|=(const rectangle& other) noexcept
{
*this = *this | other;
return *this;
}
// AND = intersect
constexpr rectangle operator&(const rectangle& other) const noexcept
{
@@ -171,6 +275,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return rectangle{ l, t, r, b };
}
constexpr rectangle& operator&=(const rectangle& other) noexcept
{
*this = *this & other;
return *this;
}
// - = subtract
some<rectangle, 4> operator-(const rectangle& other) const
{
@@ -302,6 +412,231 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return result;
}
#pragma endregion
#pragma region RECTANGLE VS POINT
// ADD will translate (offset) the rectangle by the point.
rectangle operator+(const point& point) const
{
ptrdiff_t l, t, r, b;
THROW_HR_IF(E_ABORT, !::base::CheckAdd(left(), point.x()).AssignIfValid(&l));
THROW_HR_IF(E_ABORT, !::base::CheckAdd(top(), point.y()).AssignIfValid(&t));
THROW_HR_IF(E_ABORT, !::base::CheckAdd(right(), point.x()).AssignIfValid(&r));
THROW_HR_IF(E_ABORT, !::base::CheckAdd(bottom(), point.y()).AssignIfValid(&b));
return til::rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator+=(const point& point)
{
*this = *this + point;
return *this;
}
// SUB will translate (offset) the rectangle by the point.
rectangle operator-(const point& point) const
{
ptrdiff_t l, t, r, b;
THROW_HR_IF(E_ABORT, !::base::CheckSub(left(), point.x()).AssignIfValid(&l));
THROW_HR_IF(E_ABORT, !::base::CheckSub(top(), point.y()).AssignIfValid(&t));
THROW_HR_IF(E_ABORT, !::base::CheckSub(right(), point.x()).AssignIfValid(&r));
THROW_HR_IF(E_ABORT, !::base::CheckSub(bottom(), point.y()).AssignIfValid(&b));
return til::rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator-=(const point& point)
{
*this = *this - point;
return *this;
}
#pragma endregion
#pragma region RECTANGLE VS SIZE
// ADD will grow the total area of the rectangle. The sign is the direction to grow.
rectangle operator+(const size& size) const
{
// Fetch the pieces of the rectangle.
auto l = left();
auto r = right();
auto t = top();
auto b = bottom();
// Fetch the scale factors we're using.
const auto width = size.width();
const auto height = size.height();
// Since this is the add operation versus a size, the result
// should grow the total rectangle area.
// The sign determines which edge of the rectangle moves.
// We use the magnitude as how far to move.
if (width > 0)
{
// Adding the positive makes the rectangle "grow"
// because right stretches outward (to the right).
//
// Example with adding width 3...
// |-- x = origin
// V
// x---------| x------------|
// | | | |
// | | | |
// |---------| |------------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(r, width).AssignIfValid(&r));
}
else
{
// Adding the negative makes the rectangle "grow"
// because left stretches outward (to the left).
//
// Example with adding width -3...
// |-- x = origin
// V
// x---------| |--x---------|
// | | | |
// | | | |
// |---------| |------------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(l, width).AssignIfValid(&l));
}
if (height > 0)
{
// Adding the positive makes the rectangle "grow"
// because bottom stretches outward (to the down).
//
// Example with adding height 2...
// |-- x = origin
// V
// x---------| x---------|
// | | | |
// | | | |
// |---------| | |
// | |
// |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(b, height).AssignIfValid(&b));
}
else
{
// Adding the negative makes the rectangle "grow"
// because top stretches outward (to the up).
//
// Example with adding height -2...
// |-- x = origin
// |
// | |---------|
// V | |
// x---------| x |
// | | | |
// | | | |
// |---------| |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(t, height).AssignIfValid(&t));
}
return rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator+=(const size& size)
{
*this = *this + size;
return *this;
}
// SUB will shrink the total area of the rectangle. The sign is the direction to shrink.
rectangle operator-(const size& size) const
{
// Fetch the pieces of the rectangle.
auto l = left();
auto r = right();
auto t = top();
auto b = bottom();
// Fetch the scale factors we're using.
const auto width = size.width();
const auto height = size.height();
// Since this is the subtract operation versus a size, the result
// should shrink the total rectangle area.
// The sign determines which edge of the rectangle moves.
// We use the magnitude as how far to move.
if (width > 0)
{
// Subtracting the positive makes the rectangle "shrink"
// because right pulls inward (to the left).
//
// Example with subtracting width 3...
// |-- x = origin
// V
// x---------| x------|
// | | | |
// | | | |
// |---------| |------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(r, width).AssignIfValid(&r));
}
else
{
// Subtracting the negative makes the rectangle "shrink"
// because left pulls inward (to the right).
//
// Example with subtracting width -3...
// |-- x = origin
// V
// x---------| x |------|
// | | | |
// | | | |
// |---------| |------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(l, width).AssignIfValid(&l));
}
if (height > 0)
{
// Subtracting the positive makes the rectangle "shrink"
// because bottom pulls inward (to the up).
//
// Example with subtracting height 2...
// |-- x = origin
// V
// x---------| x---------|
// | | |---------|
// | |
// |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(b, height).AssignIfValid(&b));
}
else
{
// Subtracting the positive makes the rectangle "shrink"
// because top pulls inward (to the down).
//
// Example with subtracting height -2...
// |-- x = origin
// V
// x---------| x
// | |
// | | |---------|
// |---------| |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(t, height).AssignIfValid(&t));
}
return rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator-=(const size& size)
{
*this = *this - size;
return *this;
}
#pragma endregion
constexpr ptrdiff_t top() const noexcept
{
@@ -400,6 +735,57 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !operator bool();
}
constexpr bool contains(til::point pt) const
{
return pt.x() >= _topLeft.x() && pt.x() < _bottomRight.x() &&
pt.y() >= _topLeft.y() && pt.y() < _bottomRight.y();
}
bool contains(ptrdiff_t index) const
{
return index >= 0 && index < size().area();
}
constexpr bool contains(til::rectangle rc) const
{
// Union the other rectangle and ourselves.
// If the result of that didn't grow at all, then we already
// fully contained the rectangle we were given.
return (*this | rc) == *this;
}
ptrdiff_t index_of(til::point pt) const
{
THROW_HR_IF(E_INVALIDARG, !contains(pt));
// Take Y away from the top to find how many rows down
auto check = base::CheckSub(pt.y(), top());
// Multiply by the width because we've passed that many
// widths-worth of indices.
check *= width();
// Then add in the last few indices in the x position this row
// and subtract left to find the offset from left edge.
check = check + pt.x() - left();
ptrdiff_t result;
THROW_HR_IF(E_ABORT, !check.AssignIfValid(&result));
return result;
}
til::point point_at(ptrdiff_t index) const
{
THROW_HR_IF(E_INVALIDARG, !contains(index));
const auto div = std::div(index, width());
// Not checking math on these because we're presuming
// that the point can't be in bounds of a rectangle where
// this would overflow on addition after the division.
return til::point{ div.rem + left(), div.quot + top() };
}
#ifdef _WINCONTYPES_
// NOTE: This will convert back to INCLUSIVE on the way out because
// that is generally how SMALL_RECTs are handled in console code and via the APIs.
@@ -433,6 +819,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#endif
std::wstring to_string() const
{
return wil::str_printf<std::wstring>(L"(L:%td, T:%td, R:%td, B:%td) [W:%td, H:%td]", left(), top(), right(), bottom(), width(), height());
}
protected:
til::point _topLeft;
til::point _bottomRight;
@@ -452,7 +843,7 @@ namespace WEX::TestExecution
public:
static WEX::Common::NoThrowString ToString(const ::til::rectangle& rect)
{
return WEX::Common::NoThrowString().Format(L"(L:%td, T:%td, R:%td, B:%td) [W:%td, H:%td]", rect.left(), rect.top(), rect.right(), rect.bottom(), rect.width(), rect.height());
return WEX::Common::NoThrowString(rect.to_string().c_str());
}
};

View File

@@ -64,6 +64,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !(*this == other);
}
constexpr explicit operator bool() const noexcept
{
return _width > 0 && _height > 0;
}
size operator+(const size& other) const
{
ptrdiff_t width;
@@ -215,6 +220,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#endif
std::wstring to_string() const
{
return wil::str_printf<std::wstring>(L"[W:%td, H:%td]", width(), height());
}
protected:
ptrdiff_t _width;
ptrdiff_t _height;
@@ -234,7 +244,7 @@ namespace WEX::TestExecution
public:
static WEX::Common::NoThrowString ToString(const ::til::size& size)
{
return WEX::Common::NoThrowString().Format(L"[W:%td, H:%td]", size.width(), size.height());
return WEX::Common::NoThrowString(size.to_string().c_str());
}
};

View File

@@ -127,6 +127,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !_used;
}
constexpr void clear() noexcept
{
_used = 0;
_array = {}; // should free members, if necessary.
}
constexpr const_reference at(size_type pos) const
{
if (_used <= pos)
@@ -169,6 +175,18 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
++_used;
}
void push_back(T&& val)
{
if (_used >= N)
{
_outOfRange();
}
til::at(_array, _used) = std::move(val);
++_used;
}
void pop_back()
{
if (_used <= 0)
@@ -190,6 +208,21 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
throw std::out_of_range("invalid some<T, N> subscript");
}
std::wstring to_string() const
{
std::wstringstream wss;
wss << std::endl
<< L"Some contains " << size() << " of max size " << max_size() << ":" << std::endl;
wss << L"Elements:" << std::endl;
for (auto& item : *this)
{
wss << L"\t- " << item.to_string() << std::endl;
}
return wss.str();
}
};
}
@@ -202,15 +235,7 @@ namespace WEX::TestExecution
public:
static WEX::Common::NoThrowString ToString(const ::til::some<T, N>& some)
{
auto str = WEX::Common::NoThrowString().Format(L"\r\nSome contains %d of max size %d:\r\nElements:\r\n", some.size(), some.max_size());
for (auto& item : some)
{
const auto itemStr = WEX::TestExecution::VerifyOutputTraits<T>::ToString(item);
str.AppendFormat(L"\t- %ws\r\n", (const wchar_t*)itemStr);
}
return str;
return WEX::Common::NoThrowString(some.to_string().c_str());
}
};

View File

@@ -336,7 +336,7 @@ using namespace Microsoft::Console::Interactivity;
// - STATUS_SUCCESS on success, otherwise an appropriate error.
[[nodiscard]] NTSTATUS InteractivityFactory::CreatePseudoWindow(HWND& hwnd)
{
hwnd = 0;
hwnd = nullptr;
ApiLevel level;
NTSTATUS status = ApiDetector::DetectNtUserWindow(&level);
;

View File

@@ -28,7 +28,7 @@ IConsoleWindow* ServiceLocator::s_consoleWindow = nullptr;
Globals ServiceLocator::s_globals;
bool ServiceLocator::s_pseudoWindowInitialized = false;
wil::unique_hwnd ServiceLocator::s_pseudoWindow = 0;
wil::unique_hwnd ServiceLocator::s_pseudoWindow = nullptr;
#pragma endregion

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