Compare commits

..

19 Commits

Author SHA1 Message Date
Dustin L. Howett (MSFT)
3fe7df4918 Add support for renderer backoff, don't FAIL_FAST on 3x failures, add UI (#5353)
Renderer: Add support for backoff and auto-disable on failed retry

This commit introduces a backoff (150ms * number of tries) to the
renderer's retry logic (introduced in #2830). It also changes the
FAIL_FAST to a less globally-harmful render thread disable, so that we
stop blowing up any application hosting a terminal when the graphics
driver goes away.

In addition, it adds a callback that a Renderer consumer can use to
determine when the renderer _has_ failed, and a public method to kick it
back into life.

Fixes #5340.

This PR also wires up TermControl so that it shows some UI when the renderer tastes clay.

![image](https://user-images.githubusercontent.com/14316954/79266118-f073f680-7e4b-11ea-8b96-5588a13aff3b.png)

![image](https://user-images.githubusercontent.com/14316954/79266125-f36ee700-7e4b-11ea-9314-4280e9149461.png)

* [x] Closes #5340
* [x] cla
* [ ] Tests added/passed
* [ ] Requires documentation to be updated
* [x] I've discussed this with core contributors already.

I tested this by dropping the number of retries to 1 and forcing a TDR while doing `wsl cmatrix -u0`. It picked up exactly where it left off.

As a bonus, you can actually still type into the terminal when it's graphically suspended (and `exit` still works.). The block is _entirely graphical_.
2020-04-14 16:14:04 -07:00
Chester Liu
875680a3f1 Make CodepointWidthDetector::GetWidth faster (#3727)
This is a subset of #3578 which I think is harmless and the first step towards making things right.
References #3546 #3578 

## Detailed Description of the Pull Request / Additional comments

For more robust Unicode support, `CodepointWidthDetector` should provide concrete width information rather than a simple boolean of `IsWide`. Currently only `IsWide` is widely used and optimized using quick lookup table and fallback cache. This PR moves those optimization into `GetWidth`.

## Validation Steps Performed

API remains unchanged. Things are not broken.
2020-04-14 16:12:39 -07:00
Carlos Zamora
8de952ca06 Reduce CursorChanged Events for Accessibility (#5196)
## Summary of the Pull Request
Reduce the number of times we dispatch a cursor changed event. We were firing it every time the renderer had to do anything related to the cursor. Unfortunately, blinking the cursor triggered this behavior. Now we just check if the position has changed.

## PR Checklist
* [X] Closes #5143


## Validation Steps Performed
Verified using Narrator
Also verified #3791 still works right
2020-04-14 16:12:39 -07:00
Carlos Zamora
869bcb6ec4 Set DxRenderer non-text alias mode (#5149)
There are two antialias modes that can be set on the ID2D1RenderTarget:
- one for text/glyph drawing [1]
- one for everything else [2]
We had to configure that in the RenderTarget.

Additionally, when clipping the background color rect, we need to make
sure that's aliased too. [3]

## References
[1] ID2D1RenderTarget::SetTextAntialiasMode
    https://docs.microsoft.com/en-us/windows/win32/api/d2d1/nf-d2d1-id2d1rendertarget-settextantialiasmode
[2] ID2D1RenderTarget::SetAntialiasMode
    https://docs.microsoft.com/en-us/windows/win32/api/d2d1/nf-d2d1-id2d1rendertarget-setantialiasmode)
[3] ID2D1CommandSink::PushAxisAlignedClip
    https://docs.microsoft.com/en-us/windows/win32/api/d2d1_1/nf-d2d1_1-id2d1commandsink-pushaxisalignedclip)

## Validation
Open and interact with midnight commander with the display scaling set
to...
- 100%
- 125%
- 150%
- 175%
- 200%

Closes #3626
2020-04-14 16:12:39 -07:00
Dustin L. Howett (MSFT)
1fe836eeca 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-04-14 16:12:39 -07:00
Carlos Zamora
c97f336f54 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-04-14 16:12:38 -07:00
Carlos Zamora
ad80cffedf 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-04-14 16:12:38 -07:00
Carlos Zamora
1bcc85a60b 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-04-14 16:12:38 -07:00
Dustin L. Howett (MSFT)
0b8b0b9d94 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.

(cherry picked from commit aaa4943112)
2020-03-18 18:33:23 -07:00
Mike Griese
b4dae1238e 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`

(cherry picked from commit f7d106d3f3)
2020-03-18 18:33:23 -07:00
Mike Griese
7deaf6b5aa 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

(cherry picked from commit d50409b901)
2020-03-18 18:33:23 -07:00
Mike Griese
266402a2d6 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.

(cherry picked from commit f221cd245e)
2020-03-18 18:33:23 -07:00
Dustin L. Howett (MSFT)
3d7b455bb7 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

(cherry picked from commit d392a48857)
2020-03-18 18:33:23 -07:00
Leon Liang
a6dedbb25a 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.

(cherry picked from commit cddac25726)
2020-03-18 18:33:23 -07:00
Mike Griese
37d417c07d 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.

(cherry picked from commit 8211ed9fa6)
2020-03-18 18:33:23 -07:00
Carlos Zamora
91503e0c96 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.

(cherry picked from commit 1d8c5bae35)
2020-03-18 18:33:23 -07:00
Leon Liang
9516372a8a 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

(cherry picked from commit 57c7d1d7ae)
2020-03-18 18:33:23 -07: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
76 changed files with 1211 additions and 1626 deletions

View File

@@ -110,31 +110,31 @@ For commands with arguments:
| Command | Command Description | Action (*=required) | Action Arguments | Argument Descriptions |
| ------- | ------------------- | ------ | ---------------- | ----------------- |
| closePane | Close the active pane. | | | |
| closeTab | Close the current tab. | | | |
| closeWindow | Close the current window and all tabs within it. | | | |
| copy | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
| decreaseFontSize | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
| duplicateTab | Make a copy and open the current tab. | | | |
| find | Open the search dialog box. | | | |
| increaseFontSize | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
| moveFocus | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| newTab | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| nextTab | Open the tab to the right of the current one. | | | |
| openNewTabDropdown | Open the dropdown menu. | | | |
| openSettings | Open the settings file. | | | |
| paste | Insert the content that was copied onto the clipboard. | | | |
| prevTab | Open the tab to the left of the current one. | | | |
| resetFontSize | Reset the text size to the default value. | | | |
| resizePane | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| scrollDown | Move the screen down. | | | |
| scrollUp | Move the screen up. | | | |
| scrollUpPage | Move the screen up a whole page. | | | |
| scrollDownPage | Move the screen down a whole page. | | | |
| splitPane | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
| switchToTab | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| toggleFullscreen | Switch between fullscreen and default window sizes. | | | |
| unbound | Unbind the associated keys from any command. | | | |
| `closePane` | Close the active pane. | | | |
| `closeTab` | Close the current tab. | | | |
| `closeWindow` | Close the current window and all tabs within it. | | | |
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
| `decreaseFontSize` | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
| `duplicateTab` | Make a copy and open the current tab. | | | |
| `find` | Open the search dialog box. | | | |
| `increaseFontSize` | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| `newTab` | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| `nextTab` | Open the tab to the right of the current one. | | | |
| `openNewTabDropdown` | Open the dropdown menu. | | | |
| `openSettings` | Open the settings file. | | | |
| `paste` | Insert the content that was copied onto the clipboard. | | | |
| `prevTab` | Open the tab to the left of the current one. | | | |
| `resetFontSize` | Reset the text size to the default value. | | | |
| `resizePane` | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| `scrollDown` | Move the screen down. | | | |
| `scrollUp` | Move the screen up. | | | |
| `scrollUpPage` | Move the screen up a whole page. | | | |
| `scrollDownPage` | Move the screen down a whole page. | | | |
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
| `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |
| `unbound` | Unbind the associated keys from any command. | | | |
### Accepted Modifiers and Keys

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

@@ -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

@@ -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

@@ -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

@@ -164,4 +164,10 @@
<data name="SearchBox_Close.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Close Search Box</value>
</data>
</root>
<data name="TermControl_RendererFailedTextBlock.Text" xml:space="preserve">
<value>This terminal has encountered an issue with the graphics driver and it could not recover in time. It has been suspended.</value>
</data>
<data name="TermControl_RendererRetryButton.Content" xml:space="preserve">
<value>Resume</value>
</data>
</root>

View File

@@ -522,6 +522,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
_renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
if (auto strongThis{ weakThis.get() })
{
strongThis->_RendererEnteredErrorState();
}
});
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
// Set up the DX Engine
@@ -724,6 +731,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())
@@ -2352,6 +2369,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
e.DragUIOverride().IsGlyphVisible(false);
}
// Method Description:
// - Produces the error dialog that notifies the user that rendering cannot proceed.
winrt::fire_and_forget TermControl::_RendererEnteredErrorState()
{
auto strongThis{ get_strong() };
co_await Dispatcher(); // pop up onto the UI thread
if (auto loadedUiElement{ FindName(L"RendererFailedNotice") })
{
if (auto uiElement{ loadedUiElement.try_as<::winrt::Windows::UI::Xaml::UIElement>() })
{
uiElement.Visibility(Visibility::Visible);
}
}
}
// Method Description:
// - Responds to the Click event on the button that will re-enable the renderer.
void TermControl::_RenderRetryButton_Click(IInspectable const& /*sender*/, IInspectable const& /*args*/)
{
// It's already loaded if we get here, so just hide it.
RendererFailedNotice().Visibility(Visibility::Collapsed);
_renderer->ResetErrorStateAndResume();
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View File

@@ -78,6 +78,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void ResetFontSize();
winrt::fire_and_forget SwapChainChanged();
winrt::fire_and_forget _RendererEnteredErrorState();
void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args);
void CreateSearchBoxControl();

View File

@@ -77,6 +77,23 @@
CurrentCursorPosition="_CurrentCursorPositionHandler"
CurrentFontInfo="_FontInfoHandler" />
<Grid x:Name="RendererFailedNotice"
x:Load="False"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
BorderBrush="{ThemeResource SystemAccentColor}"
Margin="8,8,8,8"
Padding="8,8,8,8"
BorderThickness="2,2,2,2"
CornerRadius="{ThemeResource OverlayCornerRadius}">
<StackPanel>
<TextBlock HorizontalAlignment="Center" x:Uid="TermControl_RendererFailedTextBlock" TextWrapping="WrapWholeWords"/>
<Button Click="_RenderRetryButton_Click" x:Uid="TermControl_RendererRetryButton" HorizontalAlignment="Right" />
</StackPanel>
</Border>
</Grid>
</Grid>
</UserControl>

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

@@ -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,7 @@ 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);
_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

@@ -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

@@ -66,6 +66,7 @@
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<LinkIncremental>false</LinkIncremental>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<ItemDefinitionGroup>

View File

@@ -359,6 +359,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

@@ -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;
}
@@ -314,3 +319,55 @@ void ConptyOutputTests::WriteAFewSimpleLines()
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

@@ -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

@@ -6,13 +6,11 @@
#include "til/at.h"
#include "til/color.h"
#include "til/some.h"
#include "til/point.h"
#include "til/size.h"
#include "til/point.h"
#include "til/rectangle.h"
#include "til/u8u16convert.h"
//#include "til/operators.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}

View File

@@ -1,347 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <vector>
#include "size.h"
#ifdef UNIT_TESTING
class BitmapTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
class const_bitterator // Bit Iterator. Bitterator.
{
public:
const_bitterator(const std::vector<bool>& values, size_t pos) :
_map(values),
_pos(pos)
{
}
const_bitterator operator+(const ptrdiff_t movement)
{
auto copy = *this;
copy += movement;
return copy;
}
const_bitterator operator-(const ptrdiff_t movement)
{
auto copy = *this;
copy -= movement;
return copy;
}
const_bitterator& operator++()
{
++_pos;
return (*this);
}
const_bitterator& operator--()
{
--_pos;
return (*this);
}
const_bitterator& operator+=(const ptrdiff_t& movement)
{
_pos += movement;
return (*this);
}
const_bitterator& operator-=(const ptrdiff_t& movement)
{
_pos -= movement;
return (*this);
}
bool operator==(const const_bitterator& other) const
{
return _pos == other._pos && _map == other._map;
}
bool operator!=(const const_bitterator& other) const
{
return !(*this == other);
}
bool operator<(const const_bitterator& other) const
{
return _pos < other._pos;
}
bool operator>(const const_bitterator& other) const
{
return _pos > other._pos;
}
bool operator*() const
{
return _map[_pos];
}
private:
size_t _pos;
const std::vector<bool>& _map;
};
class const_runerator // Run Iterator. Runerator.
{
public:
const_runerator(const std::vector<bool>& values, til::size sz, size_t pos) :
_values(values),
_size(sz),
_pos(pos)
{
_calculateArea();
}
const_runerator& operator++()
{
_pos = _nextPos;
_calculateArea();
return (*this);
}
bool operator==(const const_runerator& other) const
{
return _pos == other._pos && _values == other._values;
}
bool operator!=(const const_runerator& other) const
{
return !(*this == other);
}
bool operator<(const const_runerator& other) const
{
return _pos < other._pos;
}
bool operator>(const const_runerator& other) const
{
return _pos > other._pos;
}
til::rectangle operator*() const
{
return _run;
}
private:
const std::vector<bool>& _values;
const til::size _size;
size_t _pos;
size_t _nextPos;
til::rectangle _run;
til::point _indexToPoint(size_t index)
{
return til::point{ (ptrdiff_t)index % _size.width(), (ptrdiff_t)index / _size.width() };
}
void _calculateArea()
{
const size_t end = (size_t)_size.area();
_nextPos = _pos;
while (_nextPos < end && !_values.at(_nextPos))
{
++_nextPos;
}
if (_nextPos < end)
{
// pos is now at the first on bit.
const auto runStart = _indexToPoint(_nextPos);
const size_t rowEndIndex = (size_t)((runStart.y() + 1) * _size.width());
ptrdiff_t runLength = 0;
do
{
++_nextPos;
++runLength;
} while (_nextPos < end && _nextPos < rowEndIndex && _values.at(_nextPos));
_run = til::rectangle{ runStart, til::size{ runLength, static_cast<ptrdiff_t>(1) } };
}
else
{
_pos = _nextPos;
_run = til::rectangle{};
}
}
};
class bitmap
{
public:
using const_iterator = const const_bitterator;
bitmap() :
bitmap(0, 0)
{
}
bitmap(size_t width, size_t height) :
bitmap(til::size{ width, height })
{
}
bitmap(til::size sz) :
_size(sz),
_bits(sz.area(), true),
_empty(false)
{
}
const_iterator begin() const
{
return const_bitterator(_bits, 0);
}
const_iterator end() const
{
return const_bitterator(_bits, _size.area());
}
const_iterator begin_row(size_t row) const
{
return const_bitterator(_bits, row * _size.width());
}
const_iterator end_row(size_t row) const
{
return const_bitterator(_bits, (row + 1) * _size.width());
}
const_runerator begin_runs() const
{
return const_runerator(_bits, _size, 0);
}
const_runerator end_runs() const
{
return const_runerator(_bits, _size, _size.area());
}
void set(til::point pt)
{
_bits[pt.y() * _size.width() + pt.x()] = true;
_empty = false;
}
void reset(til::point pt)
{
_bits[pt.y() * _size.width() + pt.x()] = false;
}
void set(til::rectangle rc)
{
for (auto pt : rc)
{
set(pt);
}
}
void reset(til::rectangle rc)
{
for (auto pt : rc)
{
reset(pt);
}
}
void set_all()
{
// .clear() then .resize(_size(), true) throws an assert (unsupported operation)
// .assign(_size(), true) throws an assert (unsupported operation)
set(til::rectangle{ til::point{ 0, 0 }, _size });
}
void reset_all()
{
// .clear() then .resize(_size(), false) throws an assert (unsupported operation)
// .assign(_size(), false) throws an assert (unsupported operation)
reset(til::rectangle{ til::point{ 0, 0 }, _size });
_empty = true;
}
void resize(til::size size)
{
// Don't resize if it's not different as we mark the whole thing dirty on resize.
// TODO: marking it dirty might not be necessary or we should be smart about it
// (mark none of it dirty on resize down, mark just the edges on up?)
if (_size != size)
{
_size = size;
// .resize(_size(), true) throws an assert (unsupported operation)
_bits = std::vector<bool>(_size.area(), true);
}
}
void resize(size_t width, size_t height)
{
resize(til::size{ width, height });
}
constexpr bool empty() const
{
return _empty;
}
const til::size& size() const
{
return _size;
}
operator bool() const noexcept
{
return !_bits.empty();
}
bitmap operator+(const point& pt) const
{
auto temp = *this;
return temp += pt;
}
bitmap& operator+=(const point& pt)
{
// early return if nothing to do.
if (pt.x() == 0 && pt.y() == 0)
{
return (*this);
}
// If we're told to shift the whole thing by an entire width or height,
// the effect is to just clear the whole bitmap.
if (pt.x() >= _size.width() || pt.y() >= _size.height())
{
reset_all();
return (*this);
}
// TODO: any way to reconcile this with walk directions from scrolling apis?
// TODO: actually implement translation.
return (*this);
}
#ifdef UNIT_TESTING
friend class ::BitmapTests;
#endif
private:
bool _empty;
til::size _size;
std::vector<bool> _bits;
};
}

View File

@@ -1,12 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#ifdef UNIT_TESTING
class BitteratorTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
};

View File

@@ -1,256 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "rectangle.h"
#include "size.h"
#include "bitmap.h"
#define _TIL_INLINEPREFIX __declspec(noinline) inline
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
// RECTANGLE VS SIZE
// ADD will grow the total area of the rectangle. The sign is the direction to grow.
_TIL_INLINEPREFIX rectangle operator+(const rectangle& lhs, const size& rhs)
{
// Fetch the pieces of the rectangle.
auto l = lhs.left();
auto r = lhs.right();
auto t = lhs.top();
auto b = lhs.bottom();
// Fetch the scale factors we're using.
const auto width = rhs.width();
const auto height = rhs.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 } };
}
_TIL_INLINEPREFIX rectangle& operator+=(rectangle& lhs, const size& rhs)
{
lhs = lhs + rhs;
return lhs;
}
// SUB will shrink the total area of the rectangle. The sign is the direction to shrink.
_TIL_INLINEPREFIX rectangle operator-(const rectangle& lhs, const size& rhs)
{
// Fetch the pieces of the rectangle.
auto l = lhs.left();
auto r = lhs.right();
auto t = lhs.top();
auto b = lhs.bottom();
// Fetch the scale factors we're using.
const auto width = rhs.width();
const auto height = rhs.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 } };
}
_TIL_INLINEPREFIX rectangle& operator-=(rectangle& lhs, const size& rhs)
{
lhs = lhs - rhs;
return lhs;
}
// MUL will scale the entire rectangle by the size L/R * WIDTH and T/B * HEIGHT.
_TIL_INLINEPREFIX rectangle operator*(const rectangle& lhs, const size& rhs)
{
ptrdiff_t l;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.left()) * rhs.width()).AssignIfValid(&l));
ptrdiff_t t;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.top()) * rhs.height()).AssignIfValid(&t));
ptrdiff_t r;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.right()) * rhs.width()).AssignIfValid(&r));
ptrdiff_t b;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.bottom()) * rhs.height()).AssignIfValid(&b));
return til::rectangle{ l, t, r, b };
}
// 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() };
}
// 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());
}
}

View File

@@ -39,14 +39,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}
// This template will convert to point from anything that has an X and a Y field that appear convertible to an integer value
// This template will convert to size from anything that has an X and a Y field that appear convertible to an integer value
template<typename TOther>
constexpr point(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().X)> && std::is_integral_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
point(static_cast<ptrdiff_t>(other.X), static_cast<ptrdiff_t>(other.Y))
{
}
// This template will convert to point from anything that has a x and a y field that appear convertible to an integer value
// This template will convert to size from anything that has a x and a y field that appear convertible to an integer value
template<typename TOther>
constexpr point(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().x)> && std::is_integral_v<decltype(std::declval<TOther>().y)>, int> /*sentinel*/ = 0) :
point(static_cast<ptrdiff_t>(other.x), static_cast<ptrdiff_t>(other.y))
@@ -64,7 +64,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !(*this == other);
}
constexpr operator bool() const noexcept
operator bool() const noexcept
{
return _x != 0 || _y != 0;
}

View File

@@ -7,8 +7,6 @@
#include "size.h"
#include "some.h"
#include "recterator.h"
#ifdef UNIT_TESTING
class RectangleTests;
#endif
@@ -18,8 +16,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
class rectangle
{
public:
using const_iterator = recterator;
constexpr rectangle() noexcept :
rectangle(til::point{ 0, 0 }, til::point{ 0, 0 })
{
@@ -102,13 +98,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}
constexpr rectangle& operator=(const rectangle other) noexcept
{
_topLeft = other._topLeft;
_bottomRight = other._bottomRight;
return (*this);
}
constexpr bool operator==(const rectangle& other) const noexcept
{
return _topLeft == other._topLeft &&
@@ -126,16 +115,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
_topLeft.y() < _bottomRight.y();
}
const_iterator begin() const
{
return recterator(_topLeft, size());
}
const_iterator end() const
{
return recterator(_topLeft, size(), { _topLeft.x(), _topLeft.y() + height() });
}
// OR = union
constexpr rectangle operator|(const rectangle& other) const noexcept
{

View File

@@ -1,119 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "point.h"
#include "size.h"
#ifdef UNIT_TESTING
class RecteratorTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
class recterator
{
public:
recterator(point topLeft, size size) :
_topLeft(topLeft),
_size(size),
_current(topLeft)
{
}
recterator(point topLeft, size size, point start) :
_topLeft(topLeft),
_size(size),
_current(start)
{
}
recterator& operator++()
{
if (_current.x() + 1 >= _topLeft.x() + _size.width())
{
_current = { _topLeft.x(), _current.y() + 1 };
}
else
{
_current = { _current.x() + 1, _current.y() };
}
return (*this);
}
bool operator==(const recterator& other) const
{
return _current == other._current &&
_topLeft == other._topLeft &&
_size == other._size;
}
bool operator!=(const recterator& other) const
{
return !(*this == other);
}
bool operator<(const recterator& other) const
{
return _current < other._current;
}
bool operator>(const recterator& other) const
{
return _current > other._current;
}
point operator*() const
{
return _current;
}
protected:
point _current;
const point _topLeft;
const size _size;
#ifdef UNIT_TESTING
friend class ::RecteratorTests;
#endif
};
};
#ifdef __WEX_COMMON_H__
namespace WEX::TestExecution
{
template<>
class VerifyOutputTraits<::til::recterator>
{
public:
static WEX::Common::NoThrowString ToString(const ::til::recterator& /*rect*/)
{
return WEX::Common::NoThrowString().Format(L"Yep that's a recterator.");
}
};
template<>
class VerifyCompareTraits<::til::recterator, ::til::recterator>
{
public:
static bool AreEqual(const ::til::recterator& expected, const ::til::recterator& actual) noexcept
{
return expected == actual;
}
static bool AreSame(const ::til::recterator& expected, const ::til::recterator& actual) noexcept
{
return &expected == &actual;
}
static bool IsLessThan(const ::til::recterator& expectedLess, const ::til::recterator& expectedGreater) = delete;
static bool IsGreaterThan(const ::til::recterator& expectedGreater, const ::til::recterator& expectedLess) = delete;
static bool IsNull(const ::til::recterator& object) noexcept = delete;
};
};
#endif

View File

@@ -64,11 +64,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !(*this == other);
}
constexpr operator bool() const noexcept
{
return _width != 0 || _height != 0;
}
size operator+(const size& other) const
{
ptrdiff_t width;

View File

@@ -232,7 +232,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
return S_OK;
}
std::vector<til::rectangle> BgfxEngine::GetDirtyArea()
std::vector<SMALL_RECT> BgfxEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;

View File

@@ -69,7 +69,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
std::vector<til::rectangle> GetDirtyArea() override;
std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View File

@@ -110,7 +110,7 @@ HRESULT ScreenInfoUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple*
// TODO GH #4509: Box Selection is misrepresented here as a line selection.
UiaTextRange* result;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, wordDelimiters));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, _pData->IsBlockSelection(), wordDelimiters));
*ppUtr = result;
return S_OK;
}
@@ -147,7 +147,7 @@ HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* c
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, wordDelimiters));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, false, wordDelimiters));
*ppUtr = result;
return S_OK;
}

View File

@@ -32,9 +32,10 @@ HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const COORD start,
const COORD end,
bool blockRange,
const std::wstring_view wordDelimiters) noexcept
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, wordDelimiters);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, blockRange, wordDelimiters);
}
// returns a degenerate text range of the start of the row closest to the y value of point

View File

@@ -42,6 +42,7 @@ namespace Microsoft::Console::Interactivity::Win32
_In_ IRawElementProviderSimple* const pProvider,
_In_ const COORD start,
_In_ const COORD end,
_In_ bool blockRange = false,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override;
// range from a UiaPoint

View File

@@ -11,6 +11,8 @@ using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
static constexpr auto maxRetriesForRenderEngine = 3;
// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again.
static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
// Routine Description:
// - Creates a new renderer controller for a console.
@@ -78,8 +80,20 @@ Renderer::~Renderer()
{
if (--tries == 0)
{
FAIL_FAST_HR_MSG(E_UNEXPECTED, "A rendering engine required too many retries.");
// Stop trying.
_pThread->DisablePainting();
if (_pfnRendererEnteredErrorState)
{
_pfnRendererEnteredErrorState();
}
// If there's no callback, we still don't want to FAIL_FAST: the renderer going black
// isn't near as bad as the entire application aborting. We're a component. We shouldn't
// abort applications that host us.
return S_FALSE;
}
// Add a bit of backoff.
// Sleep 150ms, 300ms, 450ms before failing out and disabling the renderer.
Sleep(renderBackoffBaseTimeMilliseconds * (maxRetriesForRenderEngine - tries));
continue;
}
LOG_IF_FAILED(hr);
@@ -862,10 +876,8 @@ void Renderer::_PaintOverlay(IRenderEngine& engine,
// Set it up in a Viewport helper structure and trim it the IME viewport to be within the full console viewport.
Viewport viewConv = Viewport::FromInclusive(srCaView);
for (auto rect : engine.GetDirtyArea())
for (auto srDirty : engine.GetDirtyArea())
{
SMALL_RECT srDirty = rect;
// Dirty is an inclusive rectangle, but oddly enough the IME was an exclusive one, so correct it.
srDirty.Bottom++;
srDirty.Right++;
@@ -1018,3 +1030,23 @@ void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine)
THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
_rgpEngines.push_back(pEngine);
}
// Method Description:
// - Registers a callback that will be called when this renderer gives up.
// An application consuming a renderer can use this to display auxiliary Retry UI
// Arguments:
// - pfn: the callback
// Return Value:
// - <none>
void Renderer::SetRendererEnteredErrorStateCallback(std::function<void()> pfn)
{
_pfnRendererEnteredErrorState = std::move(pfn);
}
// Method Description:
// - Attempts to restart the renderer.
void Renderer::ResetErrorStateAndResume()
{
// because we're not stateful (we could be in the future), all we want to do is reenable painting.
EnablePainting();
}

View File

@@ -76,6 +76,9 @@ namespace Microsoft::Console::Render
void AddRenderEngine(_In_ IRenderEngine* const pEngine) override;
void SetRendererEnteredErrorStateCallback(std::function<void()> pfn);
void ResetErrorStateAndResume();
private:
std::deque<IRenderEngine*> _rgpEngines;
@@ -127,6 +130,8 @@ namespace Microsoft::Console::Render
// These are only actually effective/on in Debug builds when the flag is set using an attached debugger.
bool _fDebug = false;
std::function<void()> _pfnRendererEnteredErrorState;
#ifdef UNIT_TESTING
friend class ConptyOutputTests;
#endif

View File

@@ -26,6 +26,7 @@ RenderThread::~RenderThread()
if (_hThread)
{
_fKeepRunning = false; // stop loop after final run
EnablePainting(); // if we want to get the last frame out, we need to make sure it's enabled
SignalObjectAndWait(_hEvent, _hThread, INFINITE, FALSE); // signal final paint and wait for thread to finish.
CloseHandle(_hThread);
@@ -231,6 +232,11 @@ void RenderThread::EnablePainting()
SetEvent(_hPaintEnabledEvent);
}
void RenderThread::DisablePainting()
{
ResetEvent(_hPaintEnabledEvent);
}
void RenderThread::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs)
{
// When rendering takes place via DirectX, and a console application

View File

@@ -30,6 +30,7 @@ namespace Microsoft::Console::Render
void NotifyPaint() override;
void EnablePainting() override;
void DisablePainting() override;
void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) override;
private:

View File

@@ -273,7 +273,12 @@ using namespace Microsoft::Console::Render;
rect.right = std::accumulate(advancesSpan.cbegin(), advancesSpan.cend(), rect.right);
// Clip all drawing in this glyph run to where we expect.
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
// We need the AntialiasMode here to be Aliased to ensure
// that background boxes line up with each other and don't leave behind
// stray colors.
// See GH#3626 for more details.
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
// Ensure we pop it on the way out
auto popclip = wil::scope_exit([&d2dContext]() noexcept {
d2dContext->PopAxisAlignedClip();

View File

@@ -65,27 +65,26 @@ using namespace Microsoft::Console::Types;
// TODO GH 2683: The default constructor should not throw.
DxEngine::DxEngine() :
RenderEngineBase(),
_invalidMap{},
/*_isInvalidUsed{ false },
_invalidRect{ 0 },*/
_isInvalidUsed{ false },
_invalidRect{ 0 },
_invalidScroll{ 0 },
_presentParams{ 0 },
_presentReady{ false },
_presentScroll{ 0 },
/*_presentDirty{ 0 },*/
_presentDirty{ 0 },
_presentOffset{ 0 },
_isEnabled{ false },
_isPainting{ false },
_displaySizePixels{},
_displaySizePixels{ 0 },
_foregroundColor{ 0 },
_backgroundColor{ 0 },
_selectionBackground{},
_glyphCell{ 1, 1 },
_glyphCell{ 0 },
_haveDeviceResources{ false },
_retroTerminalEffects{ false },
_antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE },
_hwndTarget{ static_cast<HWND>(INVALID_HANDLE_VALUE) },
_sizeTarget{},
_sizeTarget{ 0 },
_dpi{ USER_DEFAULT_SCREEN_DPI },
_scale{ 1.0f },
_chainMode{ SwapChainMode::ForComposition },
@@ -239,8 +238,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
// Setup the viewport.
D3D11_VIEWPORT vp;
vp.Width = _displaySizePixels.width<FLOAT>();
vp.Height = _displaySizePixels.height<FLOAT>();
vp.Width = static_cast<FLOAT>(_displaySizePixels.cx);
vp.Height = static_cast<FLOAT>(_displaySizePixels.cy);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
@@ -410,8 +409,6 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
_displaySizePixels = _GetClientSize();
_invalidMap.resize(_displaySizePixels / _glyphCell);
if (createSwapChain)
{
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = { 0 };
@@ -430,15 +427,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
case SwapChainMode::ForHwnd:
{
// use the HWND's dimensions for the swap chain dimensions.
til::rectangle clientRect;
{
RECT rect = { 0 };
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
clientRect = rect;
}
RECT rect = { 0 };
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
SwapChainDesc.Width = clientRect.width<UINT>();
SwapChainDesc.Height = clientRect.height<UINT>();
SwapChainDesc.Width = rect.right - rect.left;
SwapChainDesc.Height = rect.bottom - rect.top;
// We can't do alpha for HWNDs. Set to ignore. It will fail otherwise.
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
@@ -464,8 +457,8 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
case SwapChainMode::ForComposition:
{
// Use the given target size for compositions.
SwapChainDesc.Width = _displaySizePixels.width<UINT>();
SwapChainDesc.Height = _displaySizePixels.height<UINT>();
SwapChainDesc.Width = _displaySizePixels.cx;
SwapChainDesc.Height = _displaySizePixels.cy;
// We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on.
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
@@ -540,6 +533,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
&props,
&_d2dRenderTarget));
// We need the AntialiasMode for non-text object to be Aliased to ensure
// that background boxes line up with each other and don't leave behind
// stray colors.
// See GH#3626 for more details.
_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
_d2dRenderTarget->SetTextAntialiasMode(_antialiasingMode);
RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed),
@@ -635,8 +633,8 @@ void DxEngine::_ReleaseDeviceResources() noexcept
return _dwriteFactory->CreateTextLayout(string,
gsl::narrow<UINT32>(stringLength),
_dwriteTextFormat.Get(),
_displaySizePixels.width<float>(),
_glyphCell.height() != 0 ? _glyphCell.height<float>() : _displaySizePixels.height<float>(),
gsl::narrow<float>(_displaySizePixels.cx),
_glyphCell.cy != 0 ? _glyphCell.cy : gsl::narrow<float>(_displaySizePixels.cy),
ppTextLayout);
}
@@ -693,11 +691,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
SMALL_RECT inclusive = *psrRegion;
inclusive.Right--;
inclusive.Bottom--;
_InvalidOr(inclusive);
_InvalidOr(*psrRegion);
return S_OK;
}
@@ -711,7 +705,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToExclusive();
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive();
return Invalidate(&sr);
}
@@ -759,42 +753,39 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
try
{
/*til::point delta(*pcoordDelta);*/
// TODO: do this.
POINT delta = { 0 };
delta.x = pcoordDelta->X * _glyphCell.cx;
delta.y = pcoordDelta->Y * _glyphCell.cy;
//POINT delta = { 0 };
//delta.x = pcoordDelta->X * _glyphCell.cx;
//delta.y = pcoordDelta->Y * _glyphCell.cy;
_InvalidOffset(delta);
//_InvalidOffset(delta);
_invalidScroll.cx += delta.x;
_invalidScroll.cy += delta.y;
//_invalidScroll.cx += delta.x;
//_invalidScroll.cy += delta.y;
// Add the revealed portion of the screen from the scroll to the invalid area.
const RECT display = _GetDisplayRect();
RECT reveal = display;
//// Add the revealed portion of the screen from the scroll to the invalid area.
//const RECT display = _GetDisplayRect();
//RECT reveal = display;
// X delta first
OffsetRect(&reveal, delta.x, 0);
IntersectRect(&reveal, &reveal, &display);
SubtractRect(&reveal, &display, &reveal);
//// X delta first
//OffsetRect(&reveal, delta.x, 0);
//IntersectRect(&reveal, &reveal, &display);
//SubtractRect(&reveal, &display, &reveal);
if (!IsRectEmpty(&reveal))
{
_InvalidOr(reveal);
}
//if (!IsRectEmpty(&reveal))
//{
// _InvalidOr(reveal);
//}
// Y delta second (subtract rect won't work if you move both)
reveal = display;
OffsetRect(&reveal, 0, delta.y);
IntersectRect(&reveal, &reveal, &display);
SubtractRect(&reveal, &display, &reveal);
//// Y delta second (subtract rect won't work if you move both)
//reveal = display;
//OffsetRect(&reveal, 0, delta.y);
//IntersectRect(&reveal, &reveal, &display);
//SubtractRect(&reveal, &display, &reveal);
//if (!IsRectEmpty(&reveal))
//{
// _InvalidOr(reveal);
//}
if (!IsRectEmpty(&reveal))
{
_InvalidOr(reveal);
}
}
CATCH_RETURN();
}
@@ -810,9 +801,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - S_OK
[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept
{
_invalidMap.set_all();
/*const RECT screen = _GetDisplayRect();
_InvalidOr(screen);*/
const RECT screen = _GetDisplayRect();
_InvalidOr(screen);
return S_OK;
}
@@ -837,7 +827,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - <none>
// Return Value:
// - X by Y area in pixels of the surface
[[nodiscard]] til::size DxEngine::_GetClientSize() const noexcept
[[nodiscard]] SIZE DxEngine::_GetClientSize() const noexcept
{
switch (_chainMode)
{
@@ -846,42 +836,39 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
RECT clientRect = { 0 };
LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect));
til::rectangle client{ clientRect };
SIZE clientSize = { 0 };
clientSize.cx = clientRect.right - clientRect.left;
clientSize.cy = clientRect.bottom - clientRect.top;
return client.size();
return clientSize;
}
case SwapChainMode::ForComposition:
{
try
{
// TODO: fix scale
/*return _sizeTarget * _scale;*/
return _sizeTarget;
}
CATCH_LOG();
return _sizeTarget;
SIZE size = _sizeTarget;
size.cx = static_cast<LONG>(size.cx * _scale);
size.cy = static_cast<LONG>(size.cy * _scale);
return size;
}
default:
FAIL_FAST_HR(E_NOTIMPL);
}
}
//// Routine Description:
//// - Helper to multiply all parameters of a rectangle by the font size
//// to convert from characters to pixels.
//// Arguments:
//// - cellsToPixels - rectangle to update
//// - fontSize - scaling factors
//// Return Value:
//// - <none> - Updates reference
//void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
//{
// cellsToPixels.left *= fontSize.cx;
// cellsToPixels.right *= fontSize.cx;
// cellsToPixels.top *= fontSize.cy;
// cellsToPixels.bottom *= fontSize.cy;
//}
// Routine Description:
// - Helper to multiply all parameters of a rectangle by the font size
// to convert from characters to pixels.
// Arguments:
// - cellsToPixels - rectangle to update
// - fontSize - scaling factors
// Return Value:
// - <none> - Updates reference
void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
{
cellsToPixels.left *= fontSize.cx;
cellsToPixels.right *= fontSize.cx;
cellsToPixels.top *= fontSize.cy;
cellsToPixels.bottom *= fontSize.cy;
}
// Routine Description:
// - Retrieves a rectangle representation of the pixel size of the
@@ -890,9 +877,9 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - <none>
// Return Value;
// - Origin-placed rectangle representing the pixel size of the surface
[[nodiscard]] til::rectangle DxEngine::_GetDisplayRect() const noexcept
[[nodiscard]] RECT DxEngine::_GetDisplayRect() const noexcept
{
return til::rectangle{ til::point{ 0, 0 }, _displaySizePixels };
return { 0, 0, _displaySizePixels.cx, _displaySizePixels.cy };
}
// Routine Description:
@@ -905,25 +892,22 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - <none>
void DxEngine::_InvalidOffset(POINT delta)
{
til::point pt{ delta };
_invalidMap += pt;
if (_isInvalidUsed)
{
// Copy the existing invalid rect
RECT invalidNew = _invalidRect;
//if (_isInvalidUsed)
//{
// // Copy the existing invalid rect
// RECT invalidNew = _invalidRect;
// Offset it to the new position
THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
// // Offset it to the new position
// THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
// Get the rect representing the display
const RECT rectScreen = _GetDisplayRect();
// // Get the rect representing the display
// const RECT rectScreen = _GetDisplayRect();
// Ensure that the new invalid rectangle is still on the display
IntersectRect(&invalidNew, &invalidNew, &rectScreen);
// // Ensure that the new invalid rectangle is still on the display
// IntersectRect(&invalidNew, &invalidNew, &rectScreen);
// _invalidRect = invalidNew;
//}
_invalidRect = invalidNew;
}
}
// Routine description:
@@ -935,22 +919,17 @@ void DxEngine::_InvalidOffset(POINT delta)
// - <none>
void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
{
if (_invalidMap)
{
_invalidMap.set(sr);
}
RECT region;
region.left = sr.Left;
region.top = sr.Top;
region.right = sr.Right;
region.bottom = sr.Bottom;
_ScaleByFont(region, _glyphCell);
//RECT region;
//region.left = sr.Left;
//region.top = sr.Top;
//region.right = sr.Right;
//region.bottom = sr.Bottom;
//_ScaleByFont(region, _glyphCell);
region.right += _glyphCell.cx;
region.bottom += _glyphCell.cy;
//region.right += _glyphCell.cx;
//region.bottom += _glyphCell.cy;
//_InvalidOr(region);
_InvalidOr(region);
}
// Routine Description:
@@ -959,20 +938,20 @@ void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
// - rc - Dirty pixel rectangle
// Return Value:
// - <none>
void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
void DxEngine::_InvalidOr(RECT rc) noexcept
{
//if (_isInvalidUsed)
//{
// UnionRect(&_invalidRect, &_invalidRect, &rc);
if (_isInvalidUsed)
{
UnionRect(&_invalidRect, &_invalidRect, &rc);
// const RECT rcScreen = _GetDisplayRect();
// IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
//}
//else
//{
// _invalidRect = rc;
// _isInvalidUsed = true;
//}
const RECT rcScreen = _GetDisplayRect();
IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
}
else
{
_invalidRect = rc;
_isInvalidUsed = true;
}
}
// Routine Description:
@@ -997,8 +976,25 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
// - Any DirectX error, a memory error, etc.
[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept
{
FAIL_FAST_IF_FAILED(InvalidateAll());
RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting.
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
TraceLoggingWrite(g_hDxRenderProvider,
"Invalid",
TraceLoggingInt32(_invalidRect.bottom - _invalidRect.top, "InvalidHeight"),
TraceLoggingInt32((_invalidRect.bottom - _invalidRect.top) / _glyphCell.cy, "InvalidHeightChars"),
TraceLoggingInt32(_invalidRect.right - _invalidRect.left, "InvalidWidth"),
TraceLoggingInt32((_invalidRect.right - _invalidRect.left) / _glyphCell.cx, "InvalidWidthChars"),
TraceLoggingInt32(_invalidRect.left, "InvalidX"),
TraceLoggingInt32(_invalidRect.left / _glyphCell.cx, "InvalidXChars"),
TraceLoggingInt32(_invalidRect.top, "InvalidY"),
TraceLoggingInt32(_invalidRect.top / _glyphCell.cy, "InvalidYChars"),
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidth"),
TraceLoggingInt32(_invalidScroll.cx / _glyphCell.cx, "ScrollWidthChars"),
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeight"),
TraceLoggingInt32(_invalidScroll.cy / _glyphCell.cy, "ScrollHeightChars"));
if (_isEnabled)
{
try
@@ -1008,7 +1004,8 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
{
RETURN_IF_FAILED(_CreateDeviceResources(true));
}
else if (_displaySizePixels != clientSize)
else if (_displaySizePixels.cy != clientSize.cy ||
_displaySizePixels.cx != clientSize.cx)
{
// OK, we're going to play a dangerous game here for the sake of optimizing resize
// First, set up a complete clear of all device resources if something goes terribly wrong.
@@ -1021,7 +1018,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
_d2dRenderTarget.Reset();
// Change the buffer size and recreate the render target (and surface)
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), DXGI_FORMAT_B8G8R8A8_UNORM, 0));
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0));
RETURN_IF_FAILED(_PrepareRenderTarget());
// OK we made it past the parts that can cause errors. We can release our failure handler.
@@ -1029,32 +1026,10 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
// And persist the new size.
_displaySizePixels = clientSize;
_invalidMap.resize(_displaySizePixels / _glyphCell);
}
_d2dRenderTarget->BeginDraw();
_isPainting = true;
// Walk the map for dirty rectangles and store them up.
// We're going to have to go over them multiple times, so don't spend all the iteration
// work multiple times.
for (auto it = _invalidMap.begin_runs(); it < _invalidMap.end_runs(); ++it)
{
auto rect = *it;
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
TraceLoggingWrite(g_hDxRenderProvider,
"Invalid",
TraceLoggingInt32((LONG)rect.height(), "InvalidHeightChars"),
TraceLoggingInt32((LONG)rect.width(), "InvalidWidthChars"),
TraceLoggingInt32((LONG)rect.left(), "InvalidXChars"),
TraceLoggingInt32((LONG)rect.top(), "InvalidYChars"),
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidthChars"),
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeightChars"));
_dirtyRects.push_back(rect);
}
}
CATCH_RETURN();
}
@@ -1084,19 +1059,15 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
{
if (_invalidScroll.cy != 0 || _invalidScroll.cx != 0)
{
// The scroll rect is the entire screen minus the revealed areas.
// Get the entire screen into a rectangle.
til::rectangle scrollArea = _GetDisplayRect();
_presentDirty = _invalidRect;
// Reduce the size of the rectangle by the scroll
scrollArea -= _invalidScroll;
_presentScroll = scrollArea;
const RECT display = _GetDisplayRect();
SubtractRect(&_presentScroll, &display, &_presentDirty);
_presentOffset.x = _invalidScroll.cx;
_presentOffset.y = _invalidScroll.cy;
_presentParams.DirtyRectsCount = gsl::narrow<UINT>(_dirtyRectRects.size());
_presentParams.pDirtyRects = _dirtyRectRects.data();
_presentParams.DirtyRectsCount = 1;
_presentParams.pDirtyRects = &_presentDirty;
_presentParams.pScrollOffset = &_presentOffset;
_presentParams.pScrollRect = &_presentScroll;
@@ -1117,11 +1088,8 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
}
}
_dirtyRects.clear();
_invalidMap.reset_all();
/*_invalidRect = { 0 };
_isInvalidUsed = false;*/
_invalidRect = { 0 };
_isInvalidUsed = false;
_invalidScroll = { 0 };
@@ -1198,8 +1166,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
RETURN_IF_FAILED(_CopyFrontToBack());
_presentReady = false;
_dirtyRectRects.clear();
/*_presentDirty = { 0 };*/
_presentDirty = { 0 };
_presentOffset = { 0 };
_presentScroll = { 0 };
_presentParams = { 0 };
@@ -1222,22 +1189,27 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
}
// Routine Description:
// - This paints in the back most layer of the frame with clear/nothing so it can
// be transparent if it wants to be.
// - This paints in the back most layer of the frame with the background color.
// Arguments:
// - <none>
// Return Value:
// - S_OK
[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept
{
const D2D1_COLOR_F nothing = { 0 }; // 0 alpha and color is black.
for (const D2D1_RECT_F rect : _dirtyRects)
switch (_chainMode)
{
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Scale(_glyphCell));
_d2dRenderTarget->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
case SwapChainMode::ForHwnd:
_d2dRenderTarget->FillRectangle(D2D1::RectF(static_cast<float>(_invalidRect.left),
static_cast<float>(_invalidRect.top),
static_cast<float>(_invalidRect.right),
static_cast<float>(_invalidRect.bottom)),
_d2dBrushBackground.Get());
break;
case SwapChainMode::ForComposition:
D2D1_COLOR_F nothing = { 0 };
_d2dRenderTarget->Clear(nothing);
_d2dRenderTarget->PopAxisAlignedClip();
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
break;
}
return S_OK;
@@ -1258,10 +1230,10 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
{
try
{
const til::point cellPoint{ coord };
// Calculate positioning of our origin.
const D2D1_POINT_2F origin = til::point{ coord } * _glyphCell;
D2D1_POINT_2F origin;
origin.x = static_cast<float>(coord.X * _glyphCell.cx);
origin.y = static_cast<float>(coord.Y * _glyphCell.cy);
// Create the text layout
CustomTextLayout layout(_dwriteFactory.Get(),
@@ -1269,7 +1241,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
clusters,
_glyphCell.width());
_glyphCell.cx);
// Get the baseline for this font as that's where we draw from
DWRITE_LINE_SPACING spacing;
@@ -1281,7 +1253,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
_d2dBrushBackground.Get(),
_dwriteFactory.Get(),
spacing,
_glyphCell,
D2D1::SizeF(gsl::narrow<FLOAT>(_glyphCell.cx), gsl::narrow<FLOAT>(_glyphCell.cy)),
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
// Layout then render the text
@@ -1382,18 +1354,25 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
// - rect - Rectangle to invert or highlight to make the selection area
// Return Value:
// - S_OK or relevant DirectX error.
[[nodiscard]] HRESULT DxEngine::PaintSelection(SMALL_RECT rect) noexcept
[[nodiscard]] HRESULT DxEngine::PaintSelection(const SMALL_RECT rect) noexcept
{
const auto existingColor = _d2dBrushForeground->GetColor();
_d2dBrushForeground->SetColor(_selectionBackground);
const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); });
rect.Bottom--;
rect.Right--;
RECT pixels;
pixels.left = rect.Left * _glyphCell.cx;
pixels.top = rect.Top * _glyphCell.cy;
pixels.right = rect.Right * _glyphCell.cx;
pixels.bottom = rect.Bottom * _glyphCell.cy;
D2D1_RECT_F draw = { 0 };
draw.left = static_cast<float>(pixels.left);
draw.top = static_cast<float>(pixels.top);
draw.right = static_cast<float>(pixels.right);
draw.bottom = static_cast<float>(pixels.bottom);
/* rect is SMALL_RECT */
const D2D1_RECT_F draw = til::rectangle{ rect } * _glyphCell;
_d2dRenderTarget->FillRectangle(draw, _d2dBrushForeground.Get());
return S_OK;
@@ -1421,12 +1400,16 @@ enum class CursorPaintType
return S_FALSE;
}
// Create rectangular block representing where the cursor can fill.
D2D1_RECT_F rect = til::rectangle{ til::point{ options.coordCursor } } * _glyphCell;
D2D1_RECT_F rect = { 0 };
rect.left = static_cast<float>(options.coordCursor.X * _glyphCell.cx);
rect.top = static_cast<float>(options.coordCursor.Y * _glyphCell.cy);
rect.right = static_cast<float>(rect.left + _glyphCell.cx);
rect.bottom = static_cast<float>(rect.top + _glyphCell.cy);
// If we're double-width, make it one extra glyph wider
if (options.fIsDoubleWidth)
{
rect.right += _glyphCell.width();
rect.right += _glyphCell.cx;
}
CursorPaintType paintType = CursorPaintType::Fill;
@@ -1438,7 +1421,7 @@ enum class CursorPaintType
// Enforce min/max cursor height
ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent);
ulHeight = gsl::narrow<ULONG>((_glyphCell.height() * ulHeight) / 100);
ulHeight = gsl::narrow<ULONG>((_glyphCell.cy * ulHeight) / 100);
rect.top = rect.bottom - ulHeight;
break;
}
@@ -1608,9 +1591,10 @@ CATCH_RETURN()
try
{
_glyphCell = fiFontInfo.GetSize();
const auto size = fiFontInfo.GetSize();
_invalidMap.resize(_displaySizePixels / _glyphCell);
_glyphCell.cx = size.X;
_glyphCell.cy = size.Y;
}
CATCH_RETURN();
@@ -1619,9 +1603,10 @@ CATCH_RETURN()
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
{
const auto cellSize = til::size{ viewInPixels.Dimensions() } / _glyphCell;
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.cx);
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.cy);
return Viewport::FromDimensions(viewInPixels.Origin(), cellSize);
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
}
// Routine Description:
@@ -1706,10 +1691,19 @@ float DxEngine::GetScaling() const noexcept
// - <none>
// Return Value:
// - Rectangle describing dirty area in characters.
// TODO: maybe this should be returning a ref... not a copy...
[[nodiscard]] std::vector<til::rectangle> DxEngine::GetDirtyArea()
[[nodiscard]] std::vector<SMALL_RECT> DxEngine::GetDirtyArea()
{
return _dirtyRects;
SMALL_RECT r;
r.Top = gsl::narrow<SHORT>(floor(_invalidRect.top / _glyphCell.cy));
r.Left = gsl::narrow<SHORT>(floor(_invalidRect.left / _glyphCell.cx));
r.Bottom = gsl::narrow<SHORT>(floor(_invalidRect.bottom / _glyphCell.cy));
r.Right = gsl::narrow<SHORT>(floor(_invalidRect.right / _glyphCell.cx));
// Exclusive to inclusive
r.Bottom--;
r.Right--;
return { r };
}
// Routine Description:
@@ -1721,7 +1715,7 @@ float DxEngine::GetScaling() const noexcept
// - Nearest integer short x and y values for each cell.
[[nodiscard]] COORD DxEngine::_GetFontSize() const noexcept
{
return _glyphCell;
return { gsl::narrow<SHORT>(_glyphCell.cx), gsl::narrow<SHORT>(_glyphCell.cy) };
}
// Routine Description:
@@ -1757,7 +1751,7 @@ float DxEngine::GetScaling() const noexcept
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
{ &cluster, 1 },
_glyphCell.width());
_glyphCell.cx);
UINT32 columns = 0;
RETURN_IF_FAILED(layout.GetColumns(&columns));

View File

@@ -27,9 +27,6 @@
#include <TraceLoggingProvider.h>
#include "til/bitmap.h"
#include "til/operators.h"
TRACELOGGING_DECLARE_PROVIDER(g_hDxRenderProvider);
namespace Microsoft::Console::Render
@@ -98,7 +95,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
@@ -124,7 +121,7 @@ namespace Microsoft::Console::Render
SwapChainMode _chainMode;
HWND _hwndTarget;
til::size _sizeTarget;
SIZE _sizeTarget;
int _dpi;
float _scale;
@@ -133,8 +130,8 @@ namespace Microsoft::Console::Render
bool _isEnabled;
bool _isPainting;
til::size _displaySizePixels;
til::size _glyphCell;
SIZE _displaySizePixels;
SIZE _glyphCell;
D2D1_COLOR_F _defaultForegroundColor;
D2D1_COLOR_F _defaultBackgroundColor;
@@ -143,14 +140,10 @@ namespace Microsoft::Console::Render
D2D1_COLOR_F _backgroundColor;
D2D1_COLOR_F _selectionBackground;
[[nodiscard]] til::rectangle _GetDisplayRect() const noexcept;
[[nodiscard]] RECT _GetDisplayRect() const noexcept;
til::bitmap _invalidMap;
std::vector<til::rectangle> _dirtyRects;
std::vector<RECT> _dirtyRectRects;
//bool _isInvalidUsed;
//RECT _invalidRect;
bool _isInvalidUsed;
RECT _invalidRect;
SIZE _invalidScroll;
void _InvalidOr(SMALL_RECT sr) noexcept;
@@ -159,7 +152,7 @@ namespace Microsoft::Console::Render
void _InvalidOffset(POINT pt);
bool _presentReady;
/*RECT _presentDirty;*/
RECT _presentDirty;
RECT _presentScroll;
POINT _presentOffset;
DXGI_PRESENT_PARAMETERS _presentParams;
@@ -253,7 +246,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] COORD _GetFontSize() const noexcept;
[[nodiscard]] til::size _GetClientSize() const noexcept;
[[nodiscard]] SIZE _GetClientSize() const noexcept;
[[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept;

View File

@@ -4,7 +4,6 @@
#pragma once
// This includes support libraries from the CRT, STL, WIL, and GSL
#define BLOCK_TIL
#include "LibraryIncludes.h"
#include <windows.h>
@@ -35,7 +34,4 @@
#include <dwrite_2.h>
#include <dwrite_3.h>
// Include TIL after DX so we can use the DX conversions.
#include "til.h"
#pragma hdrstop

View File

@@ -68,7 +68,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View File

@@ -16,7 +16,7 @@ using namespace Microsoft::Console::Render;
// Return Value:
// - The character dimensions of the current dirty area of the frame.
// This is an Inclusive rect.
std::vector<til::rectangle> GdiEngine::GetDirtyArea()
std::vector<SMALL_RECT> GdiEngine::GetDirtyArea()
{
RECT rc = _psInvalidData.rcPaint;

View File

@@ -14,7 +14,6 @@ Author(s):
#pragma once
#include "til/rectangle.h"
#include "../../inc/conattrs.hpp"
#include "Cluster.hpp"
#include "FontInfoDesired.hpp"
@@ -118,7 +117,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& FontInfo,
const int iDpi) noexcept = 0;
[[nodiscard]] virtual std::vector<til::rectangle> GetDirtyArea() = 0;
virtual std::vector<SMALL_RECT> GetDirtyArea() = 0;
[[nodiscard]] virtual HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept = 0;
[[nodiscard]] virtual HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateTitle(const std::wstring& newTitle) noexcept = 0;

View File

@@ -26,6 +26,7 @@ namespace Microsoft::Console::Render
virtual void NotifyPaint() = 0;
virtual void EnablePainting() = 0;
virtual void DisablePainting() = 0;
virtual void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) = 0;
protected:

View File

@@ -17,6 +17,8 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
_dispatcher{ THROW_HR_IF_NULL(E_INVALIDARG, dispatcher) },
_isPainting{ false },
_selectionChanged{ false },
_textBufferChanged{ false },
_cursorChanged{ false },
_isEnabled{ true },
_prevSelection{},
RenderEngineBase()
@@ -56,7 +58,8 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT UiaEngine::Invalidate(const SMALL_RECT* const /*psrRegion*/) noexcept
{
return S_FALSE;
_textBufferChanged = true;
return S_OK;
}
// Routine Description:
@@ -65,11 +68,21 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// Arguments:
// - pcoordCursor - the new position of the cursor
// Return Value:
// - S_FALSE
[[nodiscard]] HRESULT UiaEngine::InvalidateCursor(const COORD* const /*pcoordCursor*/) noexcept
// - S_OK
[[nodiscard]] HRESULT UiaEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
try
{
return S_FALSE;
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
// check if cursor moved
if (*pcoordCursor != _prevCursorPos)
{
_prevCursorPos = *pcoordCursor;
_cursorChanged = true;
}
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - Invalidates a rectangle describing a pixel area on the display
@@ -150,7 +163,8 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT UiaEngine::InvalidateAll() noexcept
{
return S_FALSE;
_textBufferChanged = true;
return S_OK;
}
// Routine Description:
@@ -192,10 +206,10 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
RETURN_HR_IF(S_FALSE, !_isEnabled);
// add more events here
// bool somethingToDo = _selectionChanged;
const bool somethingToDo = _selectionChanged || _textBufferChanged || _cursorChanged;
// If there's nothing to do, quick return
RETURN_HR_IF(S_FALSE, !_selectionChanged);
RETURN_HR_IF(S_FALSE, !somethingToDo);
_isPainting = true;
return S_OK;
@@ -221,9 +235,26 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
}
CATCH_LOG();
}
if (_textBufferChanged)
{
try
{
_dispatcher->SignalTextChanged();
}
CATCH_LOG();
}
if (_cursorChanged)
{
try
{
_dispatcher->SignalCursorChanged();
}
CATCH_LOG();
}
_selectionChanged = false;
_prevSelection.clear();
_textBufferChanged = false;
_cursorChanged = false;
_isPainting = false;
return S_OK;
@@ -403,7 +434,7 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// - <none>
// Return Value:
// - Rectangle describing dirty area in characters.
[[nodiscard]] std::vector<til::rectangle> UiaEngine::GetDirtyArea()
[[nodiscard]] std::vector<SMALL_RECT> UiaEngine::GetDirtyArea()
{
return { Viewport::Empty().ToInclusive() };
}

View File

@@ -71,7 +71,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
@@ -82,9 +82,12 @@ namespace Microsoft::Console::Render
bool _isEnabled;
bool _isPainting;
bool _selectionChanged;
bool _textBufferChanged;
bool _cursorChanged;
Microsoft::Console::Types::IUiaEventDispatcher* _dispatcher;
std::vector<SMALL_RECT> _prevSelection;
til::point _prevCursorPos;
};
}

View File

@@ -144,11 +144,6 @@ using namespace Microsoft::Console::Render;
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_InvalidCombine(const Viewport invalid) noexcept
{
if (_invalidMap)
{
_invalidMap.set(invalid.ToInclusive());
}
if (!_fInvalidRectUsed)
{
_invalidRect = invalid;

View File

@@ -17,9 +17,14 @@ using namespace Microsoft::Console::Types;
// Return Value:
// - The character dimensions of the current dirty area of the frame.
// This is an Inclusive rect.
std::vector<til::rectangle> VtEngine::GetDirtyArea()
std::vector<SMALL_RECT> VtEngine::GetDirtyArea()
{
return _invalidRects;
SMALL_RECT dirty = _invalidRect.ToInclusive();
if (dirty.Top < _virtualTop)
{
dirty.Top = _virtualTop;
}
return { dirty };
}
// Routine Description:

View File

@@ -27,7 +27,6 @@ using namespace Microsoft::Console::Types;
// If there's nothing to do, quick return
bool somethingToDo = _fInvalidRectUsed ||
!_invalidMap.empty() ||
(_scrollDelta.X != 0 || _scrollDelta.Y != 0) ||
_cursorMoved ||
_titleChanged;
@@ -35,14 +34,6 @@ using namespace Microsoft::Console::Types;
_quickReturn = !somethingToDo;
_trace.TraceStartPaint(_quickReturn, _fInvalidRectUsed, _invalidRect, _lastViewport, _scrollDelta, _cursorMoved);
if (somethingToDo)
{
for (auto it = _invalidMap.begin_runs(); it < _invalidMap.end_runs(); ++it)
{
_invalidRects.push_back(*it);
}
}
return _quickReturn ? S_FALSE : S_OK;
}
@@ -59,8 +50,6 @@ using namespace Microsoft::Console::Types;
{
_trace.TraceEndPaint();
_invalidMap.reset_all();
_invalidRects.clear();
_invalidRect = Viewport::Empty();
_fInvalidRectUsed = false;
_scrollDelta = { 0 };
@@ -540,7 +529,7 @@ using namespace Microsoft::Console::Types;
// before we need to print new text.
_deferredCursorPos = { _lastText.X + sNumSpaces, _lastText.Y };
if (_deferredCursorPos.X < _lastViewport.RightInclusive())
if (_deferredCursorPos.X <= _lastViewport.RightInclusive())
{
RETURN_IF_FAILED(_EraseCharacter(sNumSpaces));
}

View File

@@ -36,8 +36,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
_lastWasBold(false),
_lastViewport(initialViewport),
_invalidRect(Viewport::Empty()),
_invalidMap(initialViewport.Dimensions()),
_invalidRects(),
_fInvalidRectUsed(false),
_lastRealCursor({ 0 }),
_lastText({ 0 }),
@@ -295,8 +293,6 @@ CATCH_RETURN();
if ((oldView.Height() != newView.Height()) || (oldView.Width() != newView.Width()))
{
_invalidMap.resize(_lastViewport.Dimensions());
// Don't emit a resize event if we've requested it be suppressed
if (!_suppressResizeRepaint)
{

View File

@@ -24,8 +24,6 @@ Author(s):
#include <string>
#include <functional>
#include "til/bitmap.h"
// fwdecl unittest classes
#ifdef UNIT_TESTING
namespace TerminalCoreUnitTests
@@ -91,7 +89,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
std::vector<til::rectangle> GetDirtyArea() override;
std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
@@ -122,8 +120,6 @@ namespace Microsoft::Console::Render
COLORREF _LastBG;
bool _lastWasBold;
til::bitmap _invalidMap;
std::vector<til::rectangle> _invalidRects;
Microsoft::Console::Types::Viewport _lastViewport;
Microsoft::Console::Types::Viewport _invalidRect;

View File

@@ -355,7 +355,7 @@ bool WddmConEngine::IsInitialized()
return S_OK;
}
std::vector<til::rectangle> WddmConEngine::GetDirtyArea()
std::vector<SMALL_RECT> WddmConEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;

View File

@@ -61,7 +61,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
std::vector<til::rectangle> GetDirtyArea() override;
std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View File

@@ -45,6 +45,7 @@ public:
TEST_METHOD(TerminalInputModifierKeyTests);
TEST_METHOD(TerminalInputNullKeyTests);
TEST_METHOD(DifferentModifiersTest);
TEST_METHOD(CtrlNumTest);
wchar_t GetModifierChar(const bool fShift, const bool fAlt, const bool fCtrl)
{
@@ -511,6 +512,13 @@ void InputTest::TerminalInputModifierKeyTests()
s_pwsInputBuffer[1] = wchShifted;
fExpectedKeyHandled = true;
}
else if (ControlPressed(uiKeystate) && (vkey >= '1' && vkey <= '9'))
{
// The C-# keys get translated into very specific control
// characters that don't play nicely with this test. These keys
// are tested in the CtrlNumTest Test instead.
continue;
}
else
{
fExpectedKeyHandled = false;
@@ -684,4 +692,79 @@ void InputTest::DifferentModifiersTest()
TestKey(pInput, uiKeystate, vkey, L'/');
uiKeystate = RIGHT_ALT_PRESSED;
TestKey(pInput, uiKeystate, vkey, L'/');
// See https://github.com/microsoft/terminal/pull/4947#issuecomment-600382856
// C-? -> DEL -> 0x7f
Log::Comment(NoThrowString().Format(L"Checking C-?"));
// Use SHIFT_PRESSED to force us into differentiating between '/' and '?'
vkey = LOBYTE(VkKeyScan(L'?'));
s_pwszInputExpected = L"\x7f";
TestKey(pInput, SHIFT_PRESSED | LEFT_CTRL_PRESSED, vkey, L'?');
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED, vkey, L'?');
// C-M-/ -> 0x1b0x1f
Log::Comment(NoThrowString().Format(L"Checking C-M-/"));
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
vkey = LOBYTE(VkKeyScan(L'/'));
s_pwszInputExpected = L"\x1b\x1f";
TestKey(pInput, LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/');
TestKey(pInput, RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/');
// LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr
TestKey(pInput, RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey, L'/');
// C-M-? -> 0x1b0x7f
Log::Comment(NoThrowString().Format(L"Checking C-M-?"));
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
vkey = LOBYTE(VkKeyScan(L'?'));
s_pwszInputExpected = L"\x1b\x7f";
TestKey(pInput, SHIFT_PRESSED | LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?');
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?');
// LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey, L'?');
}
void InputTest::CtrlNumTest()
{
Log::Comment(L"Starting test...");
const TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
Log::Comment(L"Sending the various Ctrl+Num keys.");
unsigned int uiKeystate = LEFT_CTRL_PRESSED;
BYTE vkey = static_cast<WORD>('1');
s_pwszInputExpected = L"1";
TestKey(pInput, uiKeystate, vkey);
Log::Comment(NoThrowString().Format(
L"Skipping Ctrl+2, since that's supposed to send NUL, and doesn't play "
L"nicely with this test. Ctrl+2 is covered by other tests in this class."));
vkey = static_cast<WORD>('3');
s_pwszInputExpected = L"\x1b";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('4');
s_pwszInputExpected = L"\x1c";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('5');
s_pwszInputExpected = L"\x1d";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('6');
s_pwszInputExpected = L"\x1e";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('7');
s_pwszInputExpected = L"\x1f";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('8');
s_pwszInputExpected = L"\x7f";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('9');
s_pwszInputExpected = L"9";
TestKey(pInput, uiKeystate, vkey);
}

View File

@@ -185,19 +185,40 @@ static constexpr std::array<TermKeyMap, 22> s_modifierKeyMapping{
// These sequences are not later updated to encode the modifier state in the
// sequence itself, they are just weird exceptional cases to the general
// rules above.
static constexpr std::array<TermKeyMap, 6> s_simpleModifiedKeyMapping{
static constexpr std::array<TermKeyMap, 14> s_simpleModifiedKeyMapping{
TermKeyMap{ VK_BACK, CTRL_PRESSED, L"\x8" },
TermKeyMap{ VK_BACK, ALT_PRESSED, L"\x1b\x7f" },
TermKeyMap{ VK_BACK, CTRL_PRESSED | ALT_PRESSED, L"\x1b\x8" },
TermKeyMap{ VK_TAB, CTRL_PRESSED, L"\t" },
TermKeyMap{ VK_TAB, SHIFT_PRESSED, L"\x1b[Z" },
TermKeyMap{ VK_DIVIDE, CTRL_PRESSED, L"\x1F" },
// GH#3507 - We should also be encoding Ctrl+# according to the following table:
// https://vt100.net/docs/vt220-rm/table3-5.html
// * 1 and 9 do not send any special characters, but they _should_ send
// through the character unmodified.
// * 0 doesn't seem to send even an unmodified '0' through.
// * Ctrl+2 is already special-cased below in `HandleKey`, so it's not
// included here.
TermKeyMap{ static_cast<WORD>('1'), CTRL_PRESSED, L"1" },
// TermKeyMap{ static_cast<WORD>('2'), CTRL_PRESSED, L"\x00" },
TermKeyMap{ static_cast<WORD>('3'), CTRL_PRESSED, L"\x1B" },
TermKeyMap{ static_cast<WORD>('4'), CTRL_PRESSED, L"\x1C" },
TermKeyMap{ static_cast<WORD>('5'), CTRL_PRESSED, L"\x1D" },
TermKeyMap{ static_cast<WORD>('6'), CTRL_PRESSED, L"\x1E" },
TermKeyMap{ static_cast<WORD>('7'), CTRL_PRESSED, L"\x1F" },
TermKeyMap{ static_cast<WORD>('8'), CTRL_PRESSED, L"\x7F" },
TermKeyMap{ static_cast<WORD>('9'), CTRL_PRESSED, L"9" },
// These two are not implemented here, because they are system keys.
// TermKeyMap{ VK_TAB, ALT_PRESSED, L""}, This is the Windows system shortcut for switching windows.
// TermKeyMap{ VK_ESCAPE, ALT_PRESSED, L""}, This is another Windows system shortcut for switching windows.
};
const wchar_t* const CTRL_SLASH_SEQUENCE = L"\x1f";
const wchar_t* const CTRL_QUESTIONMARK_SEQUENCE = L"\x7F";
const wchar_t* const CTRL_ALT_SLASH_SEQUENCE = L"\x1b\x1f";
const wchar_t* const CTRL_ALT_QUESTIONMARK_SEQUENCE = L"\x1b\x7F";
void TerminalInput::ChangeKeypadMode(const bool applicationMode) noexcept
{
@@ -323,13 +344,73 @@ static bool _searchWithModifier(const KeyEvent& keyEvent, InputSender sender)
}
else
{
// One last check: C-/ is supposed to be C-_
// But '/' is not the same VKEY on all keyboards. So we have to
// figure out the vkey at runtime.
const BYTE slashVkey = LOBYTE(VkKeyScan(L'/'));
if (keyEvent.GetVirtualKeyCode() == slashVkey && keyEvent.IsCtrlPressed())
// One last check:
// * C-/ is supposed to be ^_ (the C0 character US)
// * C-? is supposed to be DEL
// * C-M-/ is supposed to be ^[^_
// * C-M-? is supposed to be ^[^?
//
// But this whole scenario is tricky. '/' is not the same VKEY on
// all keyboards. On USASCII keyboards, '/' and '?' share the _same_
// key. So we have to figure out the vkey at runtime, and we have to
// determine if the key that was pressed was '?' with some
// modifiers, or '/' with some modifiers.
//
// These translations are not in s_simpleModifiedKeyMapping, because
// the aformentioned fact that they aren't the same VKEY on all
// keyboards.
//
// See GH#3079 for details.
// Also see https://github.com/microsoft/terminal/pull/4947#issuecomment-600382856
// VkKeyScan will give us both the Vkey of the key needed for this
// character, and the modifiers the user might need to press to get
// this character.
const auto slashKeyScan = VkKeyScan(L'/'); // On USASCII: 0x00bf
const auto questionMarkKeyScan = VkKeyScan(L'?'); //On USASCII: 0x01bf
const auto slashVkey = LOBYTE(slashKeyScan);
const auto questionMarkVkey = LOBYTE(questionMarkKeyScan);
const auto ctrl = keyEvent.IsCtrlPressed();
const auto alt = keyEvent.IsAltPressed();
const bool shift = keyEvent.IsShiftPressed();
// From the KeyEvent we're translating, synthesize the equivalent VkKeyScan result
const auto vkey = keyEvent.GetVirtualKeyCode();
const short keyScanFromEvent = vkey |
(shift ? 0x100 : 0) |
(ctrl ? 0x200 : 0) |
(alt ? 0x400 : 0);
// Make sure the VKEY is an _exact_ match, and that the modifier
// bits also match. This handles the hypothetical case we get a
// keyscan back that's ctrl+alt+some_random_VK, and some_random_VK
// has bits that are a superset of the bits set for question mark.
const bool wasQuestionMark = vkey == questionMarkVkey && WI_AreAllFlagsSet(keyScanFromEvent, questionMarkKeyScan);
const bool wasSlash = vkey == slashVkey && WI_AreAllFlagsSet(keyScanFromEvent, slashKeyScan);
// If the key pressed was exactly the ? key, then try to send the
// appropriate sequence for a modified '?'. Otherwise, check if this
// was a modified '/' keypress. These mappings don't need to be
// changed at all.
if ((ctrl && alt) && wasQuestionMark)
{
sender(CTRL_ALT_QUESTIONMARK_SEQUENCE);
success = true;
}
else if (ctrl && wasQuestionMark)
{
sender(CTRL_QUESTIONMARK_SEQUENCE);
success = true;
}
else if ((ctrl && alt) && wasSlash)
{
sender(CTRL_ALT_SLASH_SEQUENCE);
success = true;
}
else if (ctrl && wasSlash)
{
// This mapping doesn't need to be changed at all.
sender(CTRL_SLASH_SEQUENCE);
success = true;
}

View File

@@ -1167,8 +1167,13 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
_trace.TraceCharInput(wch);
// Process "from anywhere" events first.
if (wch == AsciiChars::CAN ||
wch == AsciiChars::SUB)
const bool isFromAnywhereChar = (wch == AsciiChars::CAN || wch == AsciiChars::SUB);
// GH#4201 - If this sequence was ^[^X or ^[^Z, then we should
// _ActionExecuteFromEscape, as to send a Ctrl+Alt+key key. We should only
// do this for the InputStateMachineEngine - the OutputEngine should execute
// these from any state.
if (isFromAnywhereChar && !(_state == VTStates::Escape && _engine->DispatchControlCharsFromEscape()))
{
_ActionExecute(wch);
_EnterGround();

View File

@@ -259,6 +259,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
TEST_METHOD(SGRMouseTest_Modifiers);
TEST_METHOD(SGRMouseTest_Movement);
TEST_METHOD(SGRMouseTest_Scroll);
TEST_METHOD(CtrlAltZCtrlAltXTest);
friend class TestInteractDispatch;
};
@@ -1141,3 +1142,68 @@ void InputEngineTest::SGRMouseTest_Scroll()
// clang-format on
VerifySGRMouseData(testData);
}
void InputEngineTest::CtrlAltZCtrlAltXTest()
{
auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
VERIFY_IS_NOT_NULL(_stateMachine);
testState._stateMachine = _stateMachine.get();
// This is a test for GH#4201. See that issue for more details.
Log::Comment(L"Test Ctrl+Alt+Z and Ctrl+Alt+X, which execute from anywhere "
L"in the output engine, but should be Escape-Executed in the "
L"input engine.");
DisableVerifyExceptions disable;
{
auto inputSeq = L"\x1b\x1a"; // ^[^Z
wchar_t expectedWch = L'Z';
short keyscan = VkKeyScanW(expectedWch);
short vkey = keyscan & 0xff;
WORD scanCode = (WORD)MapVirtualKeyW(vkey, MAPVK_VK_TO_VSC);
INPUT_RECORD inputRec;
inputRec.EventType = KEY_EVENT;
inputRec.Event.KeyEvent.bKeyDown = TRUE;
inputRec.Event.KeyEvent.dwControlKeyState = LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED;
inputRec.Event.KeyEvent.wRepeatCount = 1;
inputRec.Event.KeyEvent.wVirtualKeyCode = vkey;
inputRec.Event.KeyEvent.wVirtualScanCode = scanCode;
inputRec.Event.KeyEvent.uChar.UnicodeChar = expectedWch - 0x40;
testState.vExpectedInput.push_back(inputRec);
_stateMachine->ProcessString(inputSeq);
}
{
auto inputSeq = L"\x1b\x18"; // ^[^X
wchar_t expectedWch = L'X';
short keyscan = VkKeyScanW(expectedWch);
short vkey = keyscan & 0xff;
WORD scanCode = (WORD)MapVirtualKeyW(vkey, MAPVK_VK_TO_VSC);
INPUT_RECORD inputRec;
inputRec.EventType = KEY_EVENT;
inputRec.Event.KeyEvent.bKeyDown = TRUE;
inputRec.Event.KeyEvent.dwControlKeyState = LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED;
inputRec.Event.KeyEvent.wRepeatCount = 1;
inputRec.Event.KeyEvent.wVirtualKeyCode = vkey;
inputRec.Event.KeyEvent.wVirtualScanCode = scanCode;
inputRec.Event.KeyEvent.uChar.UnicodeChar = expectedWch - 0x40;
testState.vExpectedInput.push_back(inputRec);
_stateMachine->ProcessString(inputSeq);
}
VerifyExpectedInputDrained();
}

View File

@@ -1,134 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "til/bitmap.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class BitmapTests
{
TEST_CLASS(BitmapTests);
TEST_METHOD(Construct)
{
COORD foo;
foo.X = 12;
foo.Y = 14;
til::point p(foo);
VERIFY_ARE_EQUAL(foo.X, p.x());
VERIFY_ARE_EQUAL(foo.Y, p.y());
POINT pt;
pt.x = 88;
pt.y = 98;
til::point q(pt);
VERIFY_ARE_EQUAL(pt.x, q.x());
VERIFY_ARE_EQUAL(pt.y, q.y());
SIZE sz;
sz.cx = 11;
sz.cy = 13;
til::size r(sz);
VERIFY_ARE_EQUAL(sz.cx, r.width());
VERIFY_ARE_EQUAL(sz.cy, r.height());
COORD bar;
bar.X = 57;
bar.Y = 15;
til::size s(bar);
VERIFY_ARE_EQUAL(bar.X, s.width());
VERIFY_ARE_EQUAL(bar.Y, s.height());
SIZE mapSize{ 10, 10 };
til::bitmap x(mapSize);
x.set({ 4, 4 });
til::rectangle area(til::point{ 5, 5 }, til::size{ 2, 2 });
x.set(area);
Log::Comment(L"Row 4!");
for (auto it = x.begin_row(4); it < x.end_row(4); ++it)
{
if (*it)
{
Log::Comment(L"True");
}
else
{
Log::Comment(L"False");
}
}
Log::Comment(L"All!");
auto start = x.begin();
auto end = x.end();
for (const auto& y : x)
{
if (y)
{
Log::Comment(L"True");
}
else
{
Log::Comment(L"False");
}
}
SMALL_RECT smrc;
smrc.Top = 31;
smrc.Bottom = 41;
smrc.Left = 59;
smrc.Right = 265;
til::rectangle smrectangle(smrc);
VERIFY_ARE_EQUAL(smrc.Top, smrectangle.top());
VERIFY_ARE_EQUAL(smrc.Bottom, smrectangle.bottom());
VERIFY_ARE_EQUAL(smrc.Left, smrectangle.left());
VERIFY_ARE_EQUAL(smrc.Right, smrectangle.right());
RECT bgrc;
bgrc.top = 3;
bgrc.bottom = 5;
bgrc.left = 8;
bgrc.right = 9;
til::rectangle bgrectangle(bgrc);
VERIFY_ARE_EQUAL(bgrc.top, bgrectangle.top());
VERIFY_ARE_EQUAL(bgrc.bottom, bgrectangle.bottom());
VERIFY_ARE_EQUAL(bgrc.left, bgrectangle.left());
VERIFY_ARE_EQUAL(bgrc.right, bgrectangle.right());
}
TEST_METHOD(Runerator)
{
til::bitmap foo{ til::size{ 4, 8 } };
foo.reset_all();
foo.set(til::rectangle{ til::point{ 1, 1 }, til::size{ 2, 2 } });
foo.set(til::rectangle{ til::point{ 3, 5 } });
foo.set(til::rectangle{ til::point{ 0, 6 } });
std::deque<til::rectangle> expectedRects;
expectedRects.push_back(til::rectangle{ til::point{ 1, 1 }, til::size{ 2, 1 } });
expectedRects.push_back(til::rectangle{ til::point{ 1, 2 }, til::size{ 2, 1 } });
expectedRects.push_back(til::rectangle{ til::point{ 3, 5 } });
expectedRects.push_back(til::rectangle{ til::point{ 0, 6 } });
for (auto it = foo.begin_runs(); it < foo.end_runs(); ++it)
{
const auto actual = *it;
const auto expected = expectedRects.front();
VERIFY_ARE_EQUAL(expected, actual);
expectedRects.pop_front();
}
}
};

View File

@@ -1,171 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "til/operators.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class OperatorTests
{
TEST_CLASS(OperatorTests);
TEST_METHOD(RectangleAdditionSize)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Add size to bottom and right");
{
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 33, 47 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Add size to top and left");
{
const til::size scale{ -3, -7 };
const til::rectangle expected{ 7, 13, 30, 40 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Add size to bottom and left");
{
const til::size scale{ -3, 7 };
const til::rectangle expected{ 7, 20, 30, 47 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Add size to top and right");
{
const til::size scale{ 3, -7 };
const til::rectangle expected{ 10, 13, 33, 40 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(RectangleInplaceAdditionSize)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Add size to bottom and right");
{
auto actual = start;
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 33, 47 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Add size to top and left");
{
auto actual = start;
const til::size scale{ -3, -7 };
const til::rectangle expected{ 7, 13, 30, 40 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Add size to bottom and left");
{
auto actual = start;
const til::size scale{ -3, 7 };
const til::rectangle expected{ 7, 20, 30, 47 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Add size to top and right");
{
auto actual = start;
const til::size scale{ 3, -7 };
const til::rectangle expected{ 10, 13, 33, 40 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(RectangleSubtractionSize)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Subtract size from bottom and right");
{
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 27, 33 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Subtract size from top and left");
{
const til::size scale{ -3, -7 };
const til::rectangle expected{ 13, 27, 30, 40 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Subtract size from bottom and left");
{
const til::size scale{ -3, 7 };
const til::rectangle expected{ 13, 20, 30, 33 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Subtract size from top and right");
{
const til::size scale{ 3, -6 };
const til::rectangle expected{ 10, 26, 27, 40 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(RectangleInplaceSubtractionSize)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Subtract size from bottom and right");
{
auto actual = start;
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 27, 33 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Subtract size from top and left");
{
auto actual = start;
const til::size scale{ -3, -7 };
const til::rectangle expected{ 13, 27, 30, 40 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Subtract size from bottom and left");
{
auto actual = start;
const til::size scale{ -3, 7 };
const til::rectangle expected{ 13, 20, 30, 33 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Subtract size from top and right");
{
auto actual = start;
const til::size scale{ 3, -6 };
const til::rectangle expected{ 10, 26, 27, 40 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
};

View File

@@ -168,21 +168,6 @@ class SizeTests
}
}
TEST_METHOD(Boolean)
{
const til::size empty;
VERIFY_IS_FALSE(empty);
const til::size heightOnly{ 0, 10 };
VERIFY_IS_TRUE(heightOnly);
const til::size widthOnly{ 10, 0 };
VERIFY_IS_TRUE(widthOnly);
const til::size both{ 10, 10 };
VERIFY_IS_TRUE(both);
}
TEST_METHOD(Addition)
{
Log::Comment(L"0.) Addition of two things that should be in bounds.");

View File

@@ -10,8 +10,6 @@
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="BitmapTests.cpp" />
<ClCompile Include="OperatorTests.cpp" />
<ClCompile Include="PointTests.cpp" />
<ClCompile Include="RectangleTests.cpp" />
<ClCompile Include="SizeTests.cpp" />

View File

@@ -4,12 +4,10 @@
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="BitmapTests.cpp" />
<ClCompile Include="SomeTests.cpp" />
<ClCompile Include="..\precomp.cpp" />
<ClCompile Include="u8u16convertTests.cpp" />
<ClCompile Include="SizeTests.cpp" />
<ClCompile Include="OperatorTests.cpp" />
<ClCompile Include="ColorTests.cpp" />
<ClCompile Include="PointTests.cpp" />
<ClCompile Include="RectangleTests.cpp" />

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
using namespace WEX::Common;
using namespace WEX::Logging;

View File

@@ -319,29 +319,47 @@ CodepointWidthDetector::CodepointWidthDetector() noexcept :
}
// Routine Description:
// - returns the width type of codepoint by searching the map generated from the unicode spec
// - returns the width type of codepoint as fast as we can by using quick lookup table and fallback cache.
// Arguments:
// - glyph - the utf16 encoded codepoint to search for
// Return Value:
// - the width type of the codepoint
CodepointWidth CodepointWidthDetector::GetWidth(const std::wstring_view glyph) const
{
if (glyph.empty())
THROW_HR_IF(E_INVALIDARG, glyph.empty());
if (glyph.size() == 1)
{
return CodepointWidth::Invalid;
// We first attempt to look at our custom quick lookup table of char width preferences.
const auto width = GetQuickCharWidth(glyph.front());
// If it's invalid, the quick width had no opinion, so go to the lookup table.
if (width == CodepointWidth::Invalid)
{
return _lookupGlyphWidthWithCache(glyph);
}
// If it's ambiguous, the quick width wanted us to ask the font directly, try that if we can.
// If not, go to the lookup table.
else if (width == CodepointWidth::Ambiguous)
{
if (_pfnFallbackMethod)
{
return _checkFallbackViaCache(glyph) ? CodepointWidth::Wide : CodepointWidth::Ambiguous;
}
else
{
return _lookupGlyphWidthWithCache(glyph);
}
}
// Otherwise, return Width as it is.
else
{
return width;
}
}
const auto codepoint = _extractCodepoint(glyph);
const auto it = std::lower_bound(s_wideAndAmbiguousTable.begin(), s_wideAndAmbiguousTable.end(), codepoint);
// For characters that are not _in_ the table, lower_bound will return the nearest item that is.
// We must check its bounds to make sure that our hit was a true hit.
if (it != s_wideAndAmbiguousTable.end() && codepoint >= it->lowerBound && codepoint <= it->upperBound)
else
{
return it->width;
return _lookupGlyphWidthWithCache(glyph);
}
return CodepointWidth::Narrow;
}
// Routine Description:
@@ -369,74 +387,71 @@ bool CodepointWidthDetector::IsWide(const wchar_t wch) const noexcept
// - true if codepoint is wide
bool CodepointWidthDetector::IsWide(const std::wstring_view glyph) const
{
THROW_HR_IF(E_INVALIDARG, glyph.empty());
if (glyph.size() == 1)
{
// We first attempt to look at our custom quick lookup table of char width preferences.
const auto width = GetQuickCharWidth(glyph.front());
// If it's invalid, the quick width had no opinion, so go to the lookup table.
if (width == CodepointWidth::Invalid)
{
return _lookupIsWide(glyph);
}
// If it's ambiguous, the quick width wanted us to ask the font directly, try that if we can.
// If not, go to the lookup table.
else if (width == CodepointWidth::Ambiguous)
{
if (_pfnFallbackMethod)
{
return _checkFallbackViaCache(glyph);
}
else
{
return _lookupIsWide(glyph);
}
}
// Otherwise, return Wide as True and Narrow as False.
else
{
return width == CodepointWidth::Wide;
}
}
else
{
return _lookupIsWide(glyph);
}
return GetWidth(glyph) == CodepointWidth::Wide;
}
// Routine Description:
// - checks if codepoint is wide using fallback methods.
// - returns the width type of codepoint by searching the map generated from the unicode spec
// Arguments:
// - glyph - the utf16 encoded codepoint to search for
// Return Value:
// - the width type of the codepoint
CodepointWidth CodepointWidthDetector::_lookupGlyphWidth(const std::wstring_view glyph) const
{
if (glyph.empty())
{
return CodepointWidth::Invalid;
}
const auto codepoint = _extractCodepoint(glyph);
const auto it = std::lower_bound(s_wideAndAmbiguousTable.begin(), s_wideAndAmbiguousTable.end(), codepoint);
// For characters that are not _in_ the table, lower_bound will return the nearest item that is.
// We must check its bounds to make sure that our hit was a true hit.
if (it != s_wideAndAmbiguousTable.end() && codepoint >= it->lowerBound && codepoint <= it->upperBound)
{
return it->width;
}
return CodepointWidth::Narrow;
}
// Routine Description:
// - returns the width type of codepoint using fallback methods.
// Arguments:
// - glyph - the utf16 encoded codepoint to check width of
// Return Value:
// - true if codepoint is wide or if it can't be confirmed to be narrow
bool CodepointWidthDetector::_lookupIsWide(const std::wstring_view glyph) const noexcept
// - the width type of the codepoint
CodepointWidth CodepointWidthDetector::_lookupGlyphWidthWithCache(const std::wstring_view glyph) const noexcept
{
try
{
// Use our generated table to try to lookup the width based on the Unicode standard.
const CodepointWidth width = GetWidth(glyph);
const CodepointWidth width = _lookupGlyphWidth(glyph);
// If it's ambiguous, then ask the font if we can.
if (width == CodepointWidth::Ambiguous)
{
if (_pfnFallbackMethod)
{
return _checkFallbackViaCache(glyph);
return _checkFallbackViaCache(glyph) ? CodepointWidth::Wide : CodepointWidth::Ambiguous;
}
else
{
return CodepointWidth::Ambiguous;
}
}
// If it's not ambiguous, it should say wide or narrow. Turn that into True = Wide or False = Narrow.
// If it's not ambiguous, it should say wide or narrow.
else
{
return width == CodepointWidth::Wide;
return width;
}
}
CATCH_LOG();
// If we got this far, we couldn't figure it out.
// It's better to be too wide than too narrow.
return true;
return CodepointWidth::Wide;
}
// Routine Description:

View File

@@ -34,6 +34,7 @@ namespace Microsoft::Console::Types
public:
virtual const bool IsSelectionActive() const = 0;
virtual const bool IsBlockSelection() const = 0;
virtual void ClearSelection() = 0;
virtual void SelectNewRegion(const COORD coordStart, const COORD coordEnd) = 0;
virtual const COORD GetSelectionAnchor() const noexcept = 0;

View File

@@ -256,6 +256,8 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_
return hr;
}
UiaTracing::TextProvider::GetSelection(*this, *range.Get());
LONG currentIndex = 0;
hr = SafeArrayPutElement(*ppRetVal, &currentIndex, range.Detach());
if (FAILED(hr))
@@ -265,7 +267,6 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_
return hr;
}
UiaTracing::TextProvider::GetSelection(*this, *range.Get());
return S_OK;
}

View File

@@ -128,9 +128,8 @@ HRESULT TermControlUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple
auto end = _pData->GetSelectionEnd();
_pData->GetTextBuffer().GetSize().IncrementInBounds(end, true);
// TODO GH #4509: Box Selection is misrepresented here as a line selection.
TermControlUiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<TermControlUiaTextRange>(&result, _pData, pProvider, start, end, wordDelimiters));
RETURN_IF_FAILED(MakeAndInitialize<TermControlUiaTextRange>(&result, _pData, pProvider, start, end, _pData->IsBlockSelection(), wordDelimiters));
*ppUtr = result;
return S_OK;
}
@@ -167,7 +166,7 @@ HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple*
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
TermControlUiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<TermControlUiaTextRange>(&result, _pData, pProvider, start, end, wordDelimiters));
RETURN_IF_FAILED(MakeAndInitialize<TermControlUiaTextRange>(&result, _pData, pProvider, start, end, false, wordDelimiters));
*ppUtr = result;
return S_OK;
}

View File

@@ -27,9 +27,10 @@ HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const COORD start,
const COORD end,
bool blockRange,
const std::wstring_view wordDelimiters) noexcept
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, wordDelimiters);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, blockRange, wordDelimiters);
}
// returns a degenerate text range of the start of the row closest to the y value of point

View File

@@ -41,6 +41,7 @@ namespace Microsoft::Terminal
_In_ IRawElementProviderSimple* const pProvider,
const COORD start,
const COORD end,
bool blockRange = false,
const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override;
// range from a UiaPoint

View File

@@ -23,6 +23,7 @@ try
_pData = pData;
_start = pData->GetViewport().Origin();
_end = pData->GetViewport().Origin();
_blockRange = false;
_wordDelimiters = wordDelimiters;
_id = id;
@@ -55,6 +56,7 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
_In_ const COORD start,
_In_ const COORD end,
_In_ bool blockRange,
_In_ std::wstring_view wordDelimiters) noexcept
try
{
@@ -73,6 +75,10 @@ try
_end = start;
}
// This should be the only way to set if we are a blockRange
// This is used for blockSelection
_blockRange = blockRange;
UiaTracing::TextRange::Constructor(*this);
return S_OK;
}
@@ -257,8 +263,8 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) noexc
if (unit == TextUnit_Character)
{
_end = _start;
bufferSize.IncrementInBounds(_end, true);
_start = buffer.GetGlyphStart(_start);
_end = buffer.GetGlyphEnd(_start);
}
else if (unit <= TextUnit_Word)
{
@@ -429,25 +435,11 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_
}
else
{
for (auto row = startAnchor.Y; row <= endAnchor.Y; ++row)
const auto textRects = _pData->GetTextBuffer().GetTextRects(startAnchor, endAnchor, _blockRange);
for (const auto& rect : textRects)
{
// assume that we are going to draw the entire row
COORD startCoord = { 0, row };
COORD endCoord = { viewport.RightInclusive(), row };
if (row == startAnchor.Y)
{
// first row --> reduce left side
startCoord.X = startAnchor.X;
}
if (row == endAnchor.Y)
{
// last row --> reduce right side
endCoord.X = endAnchor.X;
}
_getBoundingRect(startCoord, endCoord, coords);
_getBoundingRect(rect, coords);
}
}
@@ -534,10 +526,10 @@ try
const auto bufferSize = buffer.GetSize();
// convert _end to be inclusive
auto inclusiveEnd{ _end };
auto inclusiveEnd = _end;
bufferSize.DecrementInBounds(inclusiveEnd, true);
const auto textRects = buffer.GetTextRects(_start, inclusiveEnd);
const auto textRects = buffer.GetTextRects(_start, inclusiveEnd, _blockRange);
const auto bufferData = buffer.GetText(true,
false,
textRects);
@@ -695,9 +687,9 @@ try
}
else
{
auto temp = _end;
_pData->GetTextBuffer().GetSize().DecrementInBounds(temp);
_pData->SelectNewRegion(_start, temp);
auto inclusiveEnd = _end;
_pData->GetTextBuffer().GetSize().DecrementInBounds(inclusiveEnd);
_pData->SelectNewRegion(_start, inclusiveEnd);
}
UiaTracing::TextRange::Select(*this);
@@ -860,33 +852,20 @@ const Viewport UiaTextRangeBase::_getBufferSize() const noexcept
// - coords - vector to add the calculated coords to
// Return Value:
// - <none>
void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const COORD endAnchor, _Inout_ std::vector<double>& coords) const
void UiaTextRangeBase::_getBoundingRect(const til::rectangle textRect, _Inout_ std::vector<double>& coords) const
{
FAIL_FAST_IF(startAnchor.Y != endAnchor.Y);
FAIL_FAST_IF(startAnchor.X > endAnchor.X);
const auto viewport = _pData->GetViewport();
const auto currentFontSize = _getScreenFontSize();
const til::size currentFontSize = _getScreenFontSize();
POINT topLeft{ 0 };
POINT bottomRight{ 0 };
// startAnchor is converted to the viewport coordinate space
#pragma warning(suppress : 26496) // analysis can't see this, TODO GH: 4015 to improve Viewport to be less bad because it'd go away if ConvertToOrigin returned instead of inout'd.
auto startCoord = startAnchor;
viewport.ConvertToOrigin(&startCoord);
// we want to clamp to a long (output type), not a short (input type)
// so we need to explicitly say <long,long>
topLeft.x = base::ClampMul<long, long>(startCoord.X, currentFontSize.X);
topLeft.y = base::ClampMul<long, long>(startCoord.Y, currentFontSize.Y);
topLeft.x = base::ClampMul(textRect.left(), currentFontSize.width());
topLeft.y = base::ClampMul(textRect.top(), currentFontSize.height());
// endAnchor is converted to the viewport coordinate space
#pragma warning(suppress : 26496) // analysis can't see this, TODO GH: 4015 to improve Viewport to be less bad because it'd go away if ConvertToOrigin returned instead of inout'd.
auto endCoord = endAnchor;
viewport.ConvertToOrigin(&endCoord);
bottomRight.x = base::ClampMul<long, long>(base::ClampAdd(endCoord.X, 1), currentFontSize.X);
bottomRight.y = base::ClampMul<long, long>(base::ClampAdd(endCoord.Y, 1), currentFontSize.Y);
bottomRight.x = base::ClampMul(textRect.right(), currentFontSize.width());
bottomRight.y = base::ClampMul(textRect.bottom(), currentFontSize.height());
// convert the coords to be relative to the screen instead of
// the client window
@@ -918,7 +897,7 @@ void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const
void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount,
_In_ const TextPatternRangeEndpoint endpoint,
_Out_ gsl::not_null<int*> const pAmountMoved,
_In_ const bool preventBufferEnd) noexcept
_In_ const bool preventBufferEnd)
{
*pAmountMoved = 0;
@@ -929,23 +908,23 @@ void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount,
const bool allowBottomExclusive = !preventBufferEnd;
const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward;
const auto bufferSize = _getBufferSize();
const auto& buffer = _pData->GetTextBuffer();
bool success = true;
auto target = GetEndpoint(endpoint);
til::point target = GetEndpoint(endpoint);
while (std::abs(*pAmountMoved) < std::abs(moveCount) && success)
{
switch (moveDirection)
{
case MovementDirection::Forward:
success = bufferSize.IncrementInBounds(target, allowBottomExclusive);
success = buffer.MoveToNextGlyph(target, allowBottomExclusive);
if (success)
{
(*pAmountMoved)++;
}
break;
case MovementDirection::Backward:
success = bufferSize.DecrementInBounds(target, allowBottomExclusive);
success = buffer.MoveToPreviousGlyph(target, allowBottomExclusive);
if (success)
{
(*pAmountMoved)--;

View File

@@ -73,6 +73,7 @@ namespace Microsoft::Console::Types
_In_ IRawElementProviderSimple* const pProvider,
_In_ const COORD start,
_In_ const COORD end,
_In_ bool blockRange = false,
_In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept;
virtual HRESULT RuntimeClassInitialize(const UiaTextRangeBase& a) noexcept;
@@ -149,6 +150,7 @@ namespace Microsoft::Console::Types
// NOTE: _start is inclusive, but _end is exclusive
COORD _start{};
COORD _end{};
bool _blockRange;
// This is used by tracing to extract the text value
// that the UiaTextRange currently encompasses.
@@ -162,13 +164,13 @@ namespace Microsoft::Console::Types
const unsigned int _getViewportHeight(const SMALL_RECT viewport) const noexcept;
const Viewport _getBufferSize() const noexcept;
void _getBoundingRect(_In_ const COORD startAnchor, _In_ const COORD endAnchor, _Inout_ std::vector<double>& coords) const;
void _getBoundingRect(const til::rectangle textRect, _Inout_ std::vector<double>& coords) const;
void
_moveEndpointByUnitCharacter(_In_ const int moveCount,
_In_ const TextPatternRangeEndpoint endpoint,
gsl::not_null<int*> const pAmountMoved,
_In_ const bool preventBufferEnd = false) noexcept;
_In_ const bool preventBufferEnd = false);
void
_moveEndpointByUnitWord(_In_ const int moveCount,

View File

@@ -88,9 +88,9 @@ inline std::wstring UiaTracing::_getValue(const TextUnit unit) noexcept
void UiaTracing::TextRange::Constructor(const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::Constructor",
@@ -101,9 +101,9 @@ void UiaTracing::TextRange::Constructor(const UiaTextRangeBase& result) noexcept
void UiaTracing::TextRange::Clone(const UiaTextRangeBase& utr, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::Clone",
@@ -115,9 +115,9 @@ void UiaTracing::TextRange::Clone(const UiaTextRangeBase& utr, const UiaTextRang
void UiaTracing::TextRange::Compare(const UiaTextRangeBase& utr, const UiaTextRangeBase& other, bool result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::Compare",
@@ -130,9 +130,9 @@ void UiaTracing::TextRange::Compare(const UiaTextRangeBase& utr, const UiaTextRa
void UiaTracing::TextRange::CompareEndpoints(const UiaTextRangeBase& utr, const TextPatternRangeEndpoint endpoint, const UiaTextRangeBase& other, TextPatternRangeEndpoint otherEndpoint, int result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::CompareEndpoints",
@@ -147,9 +147,9 @@ void UiaTracing::TextRange::CompareEndpoints(const UiaTextRangeBase& utr, const
void UiaTracing::TextRange::ExpandToEnclosingUnit(TextUnit unit, const UiaTextRangeBase& utr) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::ExpandToEnclosingUnit",
@@ -161,9 +161,9 @@ void UiaTracing::TextRange::ExpandToEnclosingUnit(TextUnit unit, const UiaTextRa
void UiaTracing::TextRange::FindAttribute(const UiaTextRangeBase& utr) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::FindAttribute (UNSUPPORTED)",
@@ -174,9 +174,9 @@ void UiaTracing::TextRange::FindAttribute(const UiaTextRangeBase& utr) noexcept
void UiaTracing::TextRange::FindText(const UiaTextRangeBase& base, std::wstring text, bool searchBackward, bool ignoreCase, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::FindText",
@@ -191,9 +191,9 @@ void UiaTracing::TextRange::FindText(const UiaTextRangeBase& base, std::wstring
void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::GetAttributeValue",
@@ -206,9 +206,9 @@ void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXT
void UiaTracing::TextRange::GetBoundingRectangles(const UiaTextRangeBase& utr) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::GetBoundingRectangles",
@@ -219,9 +219,9 @@ void UiaTracing::TextRange::GetBoundingRectangles(const UiaTextRangeBase& utr) n
void UiaTracing::TextRange::GetEnclosingElement(const UiaTextRangeBase& utr) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::GetEnclosingElement",
@@ -232,9 +232,9 @@ void UiaTracing::TextRange::GetEnclosingElement(const UiaTextRangeBase& utr) noe
void UiaTracing::TextRange::GetText(const UiaTextRangeBase& utr, int maxLength, std::wstring result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::GetText",
@@ -247,9 +247,9 @@ void UiaTracing::TextRange::GetText(const UiaTextRangeBase& utr, int maxLength,
void UiaTracing::TextRange::Move(TextUnit unit, int count, int resultCount, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::Move",
@@ -263,9 +263,9 @@ void UiaTracing::TextRange::Move(TextUnit unit, int count, int resultCount, cons
void UiaTracing::TextRange::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int resultCount, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::MoveEndpointByUnit",
@@ -280,9 +280,9 @@ void UiaTracing::TextRange::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint
void UiaTracing::TextRange::MoveEndpointByRange(TextPatternRangeEndpoint endpoint, const UiaTextRangeBase& other, TextPatternRangeEndpoint otherEndpoint, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::MoveEndpointByRange",
@@ -296,9 +296,9 @@ void UiaTracing::TextRange::MoveEndpointByRange(TextPatternRangeEndpoint endpoin
void UiaTracing::TextRange::Select(const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::Select",
@@ -309,9 +309,9 @@ void UiaTracing::TextRange::Select(const UiaTextRangeBase& result) noexcept
void UiaTracing::TextRange::AddToSelection(const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::AddToSelection (UNSUPPORTED)",
@@ -322,9 +322,9 @@ void UiaTracing::TextRange::AddToSelection(const UiaTextRangeBase& result) noexc
void UiaTracing::TextRange::RemoveFromSelection(const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::RemoveFromSelection (UNSUPPORTED)",
@@ -335,9 +335,9 @@ void UiaTracing::TextRange::RemoveFromSelection(const UiaTextRangeBase& result)
void UiaTracing::TextRange::ScrollIntoView(bool alignToTop, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::ScrollIntoView",
@@ -349,9 +349,9 @@ void UiaTracing::TextRange::ScrollIntoView(bool alignToTop, const UiaTextRangeBa
void UiaTracing::TextRange::GetChildren(const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"UiaTextRange::AddToSelection (UNSUPPORTED)",
@@ -362,9 +362,9 @@ void UiaTracing::TextRange::GetChildren(const UiaTextRangeBase& result) noexcept
void UiaTracing::TextProvider::Constructor(const ScreenInfoUiaProviderBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::Constructor",
@@ -375,6 +375,7 @@ void UiaTracing::TextProvider::Constructor(const ScreenInfoUiaProviderBase& resu
void UiaTracing::TextProvider::get_ProviderOptions(const ScreenInfoUiaProviderBase& siup, ProviderOptions options) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
auto getOptions = [options]() {
@@ -387,7 +388,6 @@ void UiaTracing::TextProvider::get_ProviderOptions(const ScreenInfoUiaProviderBa
}
};
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::get_ProviderOptions",
@@ -399,6 +399,7 @@ void UiaTracing::TextProvider::get_ProviderOptions(const ScreenInfoUiaProviderBa
void UiaTracing::TextProvider::GetPatternProvider(const ScreenInfoUiaProviderBase& siup, PATTERNID patternId) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
auto getPattern = [patternId]() {
@@ -411,7 +412,6 @@ void UiaTracing::TextProvider::GetPatternProvider(const ScreenInfoUiaProviderBas
}
};
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::get_ProviderOptions",
@@ -423,6 +423,7 @@ void UiaTracing::TextProvider::GetPatternProvider(const ScreenInfoUiaProviderBas
void UiaTracing::TextProvider::GetPropertyValue(const ScreenInfoUiaProviderBase& siup, PROPERTYID propertyId) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
auto getProperty = [propertyId]() {
@@ -451,7 +452,6 @@ void UiaTracing::TextProvider::GetPropertyValue(const ScreenInfoUiaProviderBase&
}
};
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::GetPropertyValue",
@@ -463,9 +463,9 @@ void UiaTracing::TextProvider::GetPropertyValue(const ScreenInfoUiaProviderBase&
void UiaTracing::TextProvider::get_HostRawElementProvider(const ScreenInfoUiaProviderBase& siup) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::get_HostRawElementProvider (UNSUPPORTED)",
@@ -476,9 +476,9 @@ void UiaTracing::TextProvider::get_HostRawElementProvider(const ScreenInfoUiaPro
void UiaTracing::TextProvider::GetRuntimeId(const ScreenInfoUiaProviderBase& siup) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::GetRuntimeId",
@@ -489,9 +489,9 @@ void UiaTracing::TextProvider::GetRuntimeId(const ScreenInfoUiaProviderBase& siu
void UiaTracing::TextProvider::GetEmbeddedFragmentRoots(const ScreenInfoUiaProviderBase& siup) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::GetEmbeddedFragmentRoots (UNSUPPORTED)",
@@ -502,9 +502,9 @@ void UiaTracing::TextProvider::GetEmbeddedFragmentRoots(const ScreenInfoUiaProvi
void UiaTracing::TextProvider::SetFocus(const ScreenInfoUiaProviderBase& siup) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::SetFocus",
@@ -515,9 +515,9 @@ void UiaTracing::TextProvider::SetFocus(const ScreenInfoUiaProviderBase& siup) n
void UiaTracing::TextProvider::GetSelection(const ScreenInfoUiaProviderBase& siup, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::GetSelection",
@@ -529,9 +529,9 @@ void UiaTracing::TextProvider::GetSelection(const ScreenInfoUiaProviderBase& siu
void UiaTracing::TextProvider::GetVisibleRanges(const ScreenInfoUiaProviderBase& siup, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::GetVisibleRanges",
@@ -543,9 +543,9 @@ void UiaTracing::TextProvider::GetVisibleRanges(const ScreenInfoUiaProviderBase&
void UiaTracing::TextProvider::RangeFromChild(const ScreenInfoUiaProviderBase& siup, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::GetVisibleRanges",
@@ -557,6 +557,7 @@ void UiaTracing::TextProvider::RangeFromChild(const ScreenInfoUiaProviderBase& s
void UiaTracing::TextProvider::RangeFromPoint(const ScreenInfoUiaProviderBase& siup, UiaPoint point, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
auto getPoint = [point]() {
@@ -565,7 +566,6 @@ void UiaTracing::TextProvider::RangeFromPoint(const ScreenInfoUiaProviderBase& s
return stream.str();
};
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::RangeFromPoint",
@@ -578,9 +578,9 @@ void UiaTracing::TextProvider::RangeFromPoint(const ScreenInfoUiaProviderBase& s
void UiaTracing::TextProvider::get_DocumentRange(const ScreenInfoUiaProviderBase& siup, const UiaTextRangeBase& result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::GetVisibleRanges",
@@ -592,6 +592,7 @@ void UiaTracing::TextProvider::get_DocumentRange(const ScreenInfoUiaProviderBase
void UiaTracing::TextProvider::get_SupportedTextSelection(const ScreenInfoUiaProviderBase& siup, SupportedTextSelection result) noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
auto getResult = [result]() {
@@ -604,7 +605,6 @@ void UiaTracing::TextProvider::get_SupportedTextSelection(const ScreenInfoUiaPro
}
};
EnsureRegistration();
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"ScreenInfoUiaProvider::get_SupportedTextSelection",
@@ -613,4 +613,41 @@ void UiaTracing::TextProvider::get_SupportedTextSelection(const ScreenInfoUiaPro
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
}
void UiaTracing::Signal::SelectionChanged() noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"Signal::SelectionChanged",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
}
void UiaTracing::Signal::TextChanged() noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"Signal::TextChanged",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
}
void UiaTracing::Signal::CursorChanged() noexcept
{
EnsureRegistration();
if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
TraceLoggingWrite(
g_UiaProviderTraceProvider,
"Signal::CursorChanged",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
}
#pragma warning(pop)

View File

@@ -72,6 +72,14 @@ namespace Microsoft::Console::Types
static void get_SupportedTextSelection(const ScreenInfoUiaProviderBase& base, SupportedTextSelection result) noexcept;
};
class Signal final
{
public:
static void SelectionChanged() noexcept;
static void TextChanged() noexcept;
static void CursorChanged() noexcept;
};
private:
// Implement this as a singleton class.
static UiaTracing& EnsureRegistration() noexcept

View File

@@ -41,7 +41,8 @@ public:
#endif
private:
bool _lookupIsWide(const std::wstring_view glyph) const noexcept;
CodepointWidth _lookupGlyphWidth(const std::wstring_view glyph) const;
CodepointWidth _lookupGlyphWidthWithCache(const std::wstring_view glyph) const noexcept;
bool _checkFallbackViaCache(const std::wstring_view glyph) const;
static unsigned int _extractCodepoint(const std::wstring_view glyph) noexcept;