Compare commits

...

67 Commits

Author SHA1 Message Date
Dustin L. Howett
37138fcaaf Migrate spelling-0.0.21 changes from main 2021-07-30 12:34:39 -05:00
Dustin Howett
5bdb5e6caa Break a huge amount of the console
Writing CHAR_INFO doesn't work
RowImage is half-baked
Row.hpp contains eight reimplementations of the same slgotihm
move shit around
RowImage.split is fucking madness
look for TODO(DH) everywhere

however, termbench performance doubles for non-colored text.
2021-07-30 12:41:30 -05:00
Dustin Howett
d2f0f50651 Make the writers fun templates 2021-07-29 16:54:26 -05:00
Dustin Howett
271132edb4 Remove the OCI sources 2021-07-29 16:44:47 -05:00
Dustin Howett
7c5b39a041 OCI-DEP: Remove some more OCI 2021-07-29 13:25:12 -05:00
Dustin Howett
34035509a9 OCI-DEP: Remove all Attribute (Region/Count) fills 2021-07-28 15:40:32 -05:00
Dustin Howett
a13f6237ff WriteStringCOntiguous + WriteCharsLegacy[Measurement] 2021-07-28 15:12:55 -05:00
Dustin Howett
97702a1d9d WCL-HAX: fix wrap-repeat and buffer reuse bug 2021-07-27 18:48:26 -05:00
Dustin Howett
d4cdbc9b91 Update for Chester's TBCI change 2021-07-27 18:48:01 -05:00
Dustin Howett
596a8155ca Move Attr, Cwid, Data into nested type. Unify Damage interface to support returning damage for one column (replace off+size with min/max damage) and for multiple cols. 2021-07-27 18:46:44 -05:00
Dustin Howett
58ad0a34fc remove dead code, non-rle code, and de-tuplise 2021-07-27 13:53:51 -05:00
Dustin Howett
8f4c4f4916 and nuke the local copy of rle 2021-07-27 13:53:51 -05:00
Dustin Howett
8133f2856d tidy 2021-07-27 13:53:51 -05:00
Dustin Howett
f64660ac2f new cleaner variable names 2021-07-27 13:53:51 -05:00
Dustin Howett
b1a981daa9 port to Leonard's RLE 2021-07-27 13:53:48 -05:00
Dustin L. Howett
7d8df11ede wishlist 2021-07-27 13:51:49 -05:00
Dustin L. Howett
89fde46a94 Clean up the RLE defines 2021-07-27 13:51:49 -05:00
Dustin L. Howett
00af538278 HAX: rle-based row 2021-07-27 13:51:46 -05:00
Dustin Howett
227ce8ff20 beef prevention 2021-07-27 13:42:54 -05:00
Dustin Howett
99d9bac51d Kill GlyphAt (writable and const) 2021-07-27 13:42:52 -05:00
Dustin Howett
97a2dc6878 Rewrite Reflow 2021-07-27 13:41:53 -05:00
Dustin Howett
abf66b2ff8 NO TESTS: Add OututCellIterator that takes TextBufferCellIterator 2021-07-27 13:38:05 -05:00
Floris Westerman
3f5f37d910 Fix: Multimedia Key Hotkey Support (#10801)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Fixes/implements #10058 according to directions in that issue: added support for browser navigation keys to be used in actions.

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

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #10058
* [x] CLA signed.
* [x] Tests added/passed
* [x] Documentation updated: . If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: https://github.com/MicrosoftDocs/terminal/pull/371
* [x] Schema updated.
* [x] I've discussed this with core contributors already. According to instructions in #10058

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
The mouse back/forward keys do not correspond to the keys added here. That would be a nice (but more complicated) addition, I'll add an issue for it.
2021-07-27 17:11:51 +00:00
Chester Liu
37e0614554 Optimize hot path in textBufferCellIterator (#10621)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

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

The `+=` operator is an extremely hot path under heavily output load. This PR aims to optimize its speed.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Supports #10563
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-07-27 15:09:56 +00:00
Dustin L. Howett
d43a14c63f Replace the placeholder release build with our real one (#10778)
This pull request ports our old release pipeline from Azure DevOps' editor to real YAML.

It includes the following changes on top of a straight-up "export" from Azure:

- Converts all queue-time variables into form-based parameters
- Adds a "matrix" build strategy for Configs * Platforms
- Renames all jobs to have reasonable names
- The YAML generator has a bug where it inlines scripts *and* file paths if a task had both; remove old inlines
- Removes dead rules
- Fixes the WPF build to include the apiset impostor
- Migrates the access token into the environment for the one build stage that needs it
- Cleans up some of the online script logic
- Removes all of the "!is pull request?" checks
2021-07-27 01:14:59 +00:00
Michael Niksa
862217b04b [DefApp] Teach connection and tab to negotiate initial size (#10772)
- For tabs started from the Terminal, the initial sizing information is
  passed into the connection and used to establish the PTY. Those
  parameters are given over to the `OpenConsole.exe` acting as PTY to
  establish the initial buffer/window size.
- However, for tabs started from outside, the PTY is created with some
  default buffer information FIRST as the Terminal hasn't even been
  involved yet. As such, when the Terminal gets that connection, it must
  tell the PTY to resize just as it connects to match the window size
  it's about to use.
- Ongoing resize operations in the Terminal did and still work fine
  because they transmitted the updated size with the
  `ResizePseudoConsole` API.

## Validation Steps Performed
- [x] Confirmed existing tabs opening have correct initial size in PTY
  (like with CMD `mode con` command)
- [x] Confirmed inbound cmd tabs have correct initial size in PTY via
  `mode con` command per bug repro

Closes #9811
2021-07-26 19:31:48 +00:00
PankajBhojwani
3a71ead757 Remove some unnecessary font features from our default feature list (#10774)
Turns out, DWrite will automatically turn some features on even if they weren't included in the feature vector passed into it. Remove these features from our default list for easier readability.
2021-07-26 16:27:07 +00:00
Leonard Hecker
20e88d3e3e Fix conhost UseDx mode (#10770)
This commit fixes the UseDx mode for conhost.
In order to add support for UseDx without calling `SetWindowSize`,
responsibility for resizing `_invalidMap` has been moved to occur
only when the renderer itself recognizes a new size. Furthermore
`InvalidateAll` is now the central point to invalidate `_invalidMap`.

## Validation Steps Performed

* Enabling `UseDx` enables the DxEngine for conhost ✔️
* Resizing windows in conhost works ✔️
* Resizing windows in WT works ✔️

Closes #5455
2021-07-23 20:19:07 +02:00
Schuyler Rosefield
3ffaa1714a Update NavigateFocus function to use new visual-based navigation (#10756)
## Summary of the Pull Request
Uses the new logic to find visual neighbors of a pane to find which pane is the target when the move-focus commands are used. 

## References
It sounds like this logic will be refined later to meet #4692 

## PR Checklist
* [x] Closes #2398
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
Created a grid of panes and confirmed that focus movement went to the right quadrant instead of just the first child of the sibling.
2021-07-23 00:09:47 +00:00
PankajBhojwani
4c16cb278e Allow users to set font features and font axes (#10525)
Adds support for users to be able to set font features and axes (see the spec for more details!)

## Detailed Description

**CustomTextLayout**
- Asks the `DxFontRenderData` for the font features when getting glyphs
- _If any features have been set/updated, we always skip the "isTextSimple" shortcut_
- Asks the `_formatInUse` for any font axes when mapping characters in `_AnalyzeFontFallback`

**DxFontRenderData**
- Stores a map of font features (initialized to the [standard feature list])
- Stores a map of font axes
- Has methods to add font features/axes to the map or update existing ones
- Has methods to retrieve the font features/axes
- Sets the font axes in the `IDWriteTextFormat` when creating it

## Validation Steps Performed
It works!

[standard feature list]: ac5aef67d1/DrawableObject.ixx (L802)

Specified in #10457
Related to #1790 
Closes #759
Closes #5828
2021-07-22 23:15:44 +00:00
Mike Griese
335f69e099 Clamp the focusTab action to the number of available tabs (#10651)
## Summary of the Pull Request

When we perform a `focusTab` action, we currently do nothing if the parameter was greater than the number of tabs. This PR changes that behavior. Now, `focus-tab -t 999999` will always focus the last tab, instead of silently doing nothing. 

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

## Validation Steps Performed
* [x] ran tests
* [x] validated commandline manually
2021-07-22 13:48:36 +00:00
Schuyler Rosefield
cf97a9f772 Preliminary work to add Swap Panes functionality (GH Issues 1000, 4922) (#10638)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Add functionality to swap a pane with an adjacent (Up/Down/Left/Right) neighbor.

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References
This work potentially touches on: #1000 #2398 and #4922
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes a component of #1000 (partially, comment), #4922 (partially, `SwapPanes` function is added but not hooked up, no detach functionality)
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [x] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

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

Its been a while since I've written C++ code, and it is my first time working on a Windows application. I hope that I have not made too many mistakes.

Work currently done:
- Add boilerplate/infrastructure for argument parsing, hotkeys, event handling
- Adds the `MovePane` function that finds the focused pane, and then tries to find
  a pane that is visually adjacent to according to direction.
- First pass at the `SwapPanes` function that swaps the tree location of two panes
- First working version of helpers `_FindFocusAndNeighbor` and `_FindNeighborFromFocus`
  that search the tree for the currently focused pane, and then climbs back up the tree
  to try to find a sibling pane that is adjacent to it. 
- An `_IsAdjacent' function that tests whether two panes, given their relative offsets, are adjacent to each other according to the direction.

Next steps:
- Once working these functions (`_FindFocusAndNeighbor`, etc) could be utilized to also solve #2398 by updating the `NavigateFocus` function.
- Do we want default hotkeys for the new actions?

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
At this point, compilation and manual testing of functionality (with hotkeys) by creating panes, adding distinguishers to each pane, and then swapping them around to confirm they went to the right location.
2021-07-22 12:53:03 +00:00
Michael Niksa
41ade2c57e Pass inbound handoff message via heap so it cannot race out of scope by the time it reaches the ConsoleIoThread (#10751)
Pass inbound handoff message via heap so it cannot race out of scope by the time it reaches the ConsoleIoThread

## PR Checklist
* [x] Closes #10251
* [x] I work here.
* [x] Manually verified somewhat

## Detailed Description of the Pull Request / Additional comments
- `OpenConsole.exe` is started in response to the OS `conhost.exe` request for a handoff and prepares an Out Of Proc Multithreaded COM server.
- A COM thread from the pool inside `OpenConsole.exe` picks up the inbound message and allocates some stack space for the `CONSOLE_API_MSG` coming in
- That COM thread calls down to set up the I/O thread that will pump the console driver handle and passes a pointer to the stack-allocated `CONSOLE_API_MSG` as the `LPVOID` parameter for starting the thread.

Now one of two things happen:
1. The I/O thread is scheduled pretty much immediately (or soon enough that the COM thread hasn't messed with the stack space), picks up the pointer to the COM thread's stack with `CONSOLE_API_MSG`, and processes the initial message correctly.
2. The COM thread continues and finalizes the handoff message to `conhost.exe` declaring success. It then pops stack and "frees" the memory space. If it doesn't manage to overwrite it, we're still good. If it does, then things go crazy.

This fix changes it so that the `CONSOLE_API_MSG` is sent into the heap before being passed to the other thread so it's in a known location that won't be freed or overwritten unexpectedly.

## Validation Steps Performed
- [x] - Confirmed that many handoffs from the run box seem to work alright on my system after this change.
- [x] - Confirmed that many tab creations/splits seem to work alright on my system after this change.
- [x] - Would prefer if @ianjoneill could try to F5 this branch to build/deploy it, set it as default, and see if it makes it go away completely... but I'm pretty confident it is this based on the dumps provided either way.
2021-07-22 12:51:30 +00:00
PankajBhojwani
d1f152adcf Don't auto-generate the hidden field when creating profile stubs (#10714)
## Summary of the Pull Request
We no longer automatically write the 'hidden' field for profile stubs we create

**Note**: This does not retroactively remove the automatically generated hidden fields in current settings files

## PR Checklist
* [x] Closes #10539 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [x] I work here

## Validation Steps Performed
Deleted the ubuntu stub in my settings file, booted up terminal, new created stub did not have the hidden field. Created a fragment that overrides the hidden field and it worked.
2021-07-21 22:41:11 +00:00
Leonard Hecker
8779249b12 Release unneeded memory more eagerly from conhost (#10738)
The `_CONSOLE_API_MSG` buffer is resized to cover an entire message.
Later on any UTF-8 data is cached in a separate temporary
buffer inside `til::u8state` to prevent lone surrogate pairs.

Both cases are problematic as neither buffer is freed after the read
has finished. Passing a 100MB buffer to conhost once will thus cause it
to continue using ~220MB of physical memory until the conhost process exits.

This change releases unneeded memory as soon as the requested buffer
size has halved. In practice this means that once a command has returned
all buffers will shrink, as the shell commonly sends very small messages.

## PR Checklist
* [x] Closes #10731
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

* Buffers aren't reallocated during printing ✔️
* Buffers shrink after printing finished ✔️
2021-07-21 05:59:57 +00:00
Leonard Hecker
10b12ac90c Introduce vk() and sc() key chord specifiers (#10666)
This commit introduces an alternative to specifying key bindings as a combination of key modifiers and a character. It allows you to specify an explicit virtual key as `vk(nnn)`.
Additionally this commit makes it possible to bind actions to scan codes. As scan code 41 appears to be the button below the Escape key on virtually all keyboards, we'll be able to bind the quake mode hotkey to `win+sc(41)` and have it work consistently across most if not all keyboard layouts.

## PR Checklist
* [x] Closes #7539, Closes #10203
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

The following was tested both on US and DE keyboard layouts:
* Ctrl+, opens settings ✔️
* Win+` opens quake mode window ✔️
* Ctrl+plus/minus increase/decrease font size ✔️
2021-07-20 22:34:51 +00:00
PankajBhojwani
6ce2543a94 Fix mouse coordinates when viewport is scrolled (#10642)
## Summary of the Pull Request
Adjust the y-coordinate of the mouse coordinates we send based on how much the viewport has been scrolled

## Validation Steps Performed
Validated: cannot repro the issue in #10190 

Closes #10190
2021-07-20 21:39:55 +00:00
Mike Griese
5a5902d580 Prevent the quake window's borders from hanging onto adjacent monitors (#10676)
## Summary of the Pull Request

We were making the quake window exactly the width of the monitor it was on, but that didn't account for the 1px of border on either side.		

## References
* megathread: #8888

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

## Validation Steps Performed

It happened before, it doesn't anymore.
2021-07-20 21:04:41 +00:00
Mim van den Bos
0fefdac414 Add background color to grid to prevent animation overflow (#10716)
## Summary of the Pull Request
Add an explicit background color to part of the settings UI to prevent animation overflow. The previous solution (adding a ScrollViewer) caused problems.

## References
#10619 adds a ScrollViewer for one of the issues in #10609

## PR Checklist
* [x] Closes #10664
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
Visually confirmed the animation doesn't overflow, changed the theme and confirmed the colors are responsive. Confirmed the extra scrollbar is gone.
2021-07-20 19:04:18 +00:00
Leonard Hecker
79115e2058 Fix building with v143 toolchain (#10727)
Visual Studio 2022 Preview recently released the v143 toolchain.
C4189 is now flagging several unused variables, which breaks our build.

## PR Checklist

* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

* CascadiaPackage builds ✔️
* All tests build ✔️
2021-07-20 19:00:49 +02:00
Mike Griese
9c1331ab2e Re-evaluate the size of the quake window when it's summoned to a monitor (#10674)
## Summary of the Pull Request

When the quake window is moved to another monitor, re-evaluate it's size for that monitor.

## References
* megathread: #8888
* Similar, but not the same: #10274

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

## Detailed Description of the Pull Request / Additional comments

We'll probably need to do this in a few more places, but I'm breaking PRs into small chunks for easier reviews.

## Validation Steps Performed

Summoned the window to a bunch of different resolutions. Where it would use the wrong size before, it no longer does.
2021-07-20 16:26:35 +00:00
Mike Griese
5f2ac4e3e7 Add tracelogging for drag&drop on the new tab button (#10726)
As discussed in team sync. Is this a mysterious dark pattern we didn't know about? 

* [x] closes #10721
* [x] I work here
* [x] doesn't need tests
* [x] doesn't need docs
* see also #10160
2021-07-20 16:19:48 +00:00
Mike Griese
6e70c4ae07 Switch Connections to use ValueSets to initialize them (#10184)
#### ⚠️ targets #10051 

## Summary of the Pull Request

This PR does one big, primary thing. It removes all the constructors from any TerminalConnections, and changes them to use an `Initialize` method that accepts a `ValueSet` of properties.

Why?

For the upcoming window/content process work, we'll need the content process to be able to initialize the connection _in the content process_. However, the window process will be the one that knows what type of connection to make. Enter `ConnectionInformation`. This class will let us specify the class name of the type we want to create, and a set of settings to use when initializing that connection.

**IMPORTANT**: As a part of this, the constructor for a connection must have 0 arguments. `RoActivateInstance` lets you just conjure a WinRT type just by class name, but that class must have a 0 arg ctor. Hence the need for `Initialize`, to actually pass the settings.

We're using a `ValueSet` here because it's basically a json blob, with more steps. In the future, when extension authors want to have custom connections, we can always deserialize the json into a `ValueSet`, pass it to their connection's `Initialize`, and let then get what they need out of it.

## References
* Tear-out: #1256
* Megathread: #5000
* Project: https://github.com/microsoft/terminal/projects/5

## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/projects/5#card-50760298
* [x] I work here
* [n/a] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

`ConnectionInformation` was included as a part of this PR, to demonstrate how this will eventually be used. `ConnectionInformation` is not _currently_ used.

## Validation Steps Performed

It still builds and runs.
2021-07-20 15:02:17 +00:00
Daniel599
b05a557f48 implement drag&drop path in '+' button (#10073) (#10160)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
This PR implements the ability to drop directories/files on the '+' button which in turn will open the tab/pane/window in the given starting path.
In order to do this, I refactored the click's lambda into a method and re-used it
Sadly I wasn't able to add note about the alt/shift feature (any ideas how to do this?)
Also most of the code is "look-a-like" from other places within the project, as I don't have much experience in windows development.
 
<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References
implements #10073

## PR Checklist
* [ ] Closes #10073
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
** tests were done manually both of the old feature (alt/shift+click) on the '+' and on the profiles
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
** no idea what to add there, if any.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
tested manually.
2021-07-20 14:26:35 +00:00
Kayla Cinnamon
2e246123cf Add experimental.input.forceVT to JSON schema (#10715)
## Summary of the Pull Request
Looks like we forgot to add `experimental.input.forceVT` to the JSON schema when it was implemented in #6309
2021-07-20 09:16:40 -05:00
Chester Liu
fb69aecb19 Defer cursor winrt event triggering (#10685)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

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

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-07-20 14:05:45 +00:00
Dustin L. Howett
730d6960ab Make sure we terminate the expected title string (#10711)
When you use the size parameter to WideCharToMultiByte, it only
null-terminates the output string if the input string was
null-terminated within the specified range.

Burned in for 1k runs-

BEFORE

    Summary: Total=1000, Passed=997, Failed=3

AFTER

    Summary: Total=1000, Passed=1000, Failed=0

Fixes MSFT-34656993
2021-07-20 14:04:53 +00:00
Mike Griese
7f3bc3cb04 Only access ControlInteractivity through the projection (#10051)
## Summary of the Pull Request

This forces the `TermControl` to only use `ControlCore` and `ControlInteractivity` via their WinRT projections. We want this, because WinRT projections can be used across process boundaries. In the future, `ControlCore` and `ControlInteractivity` are going to be living in a different process entirely from `TermControl`. By enforcing this boundary now, we can make sure that they will work seamlessly in the future.

## References
* Tear-out: #1256
* Megathread: #5000
* Project: https://github.com/microsoft/terminal/projects/5

## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/projects/5#card-50760270
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

Most all this was just converting pure c++ types to winrt types when possible. I've added a couple helper projections with `til` converters, which made most of this really easy.

The "`MouseButtonState` needs to be composed of `Int32`s instead of `bool`s" is MENTAL. I have no idea why this is, but when I had the control OOP in the sample, that would crash when trying to de-marshal the bools. BODGY.

The biggest changes are in the way the UIA stuff is hooked up. The UiaEngine needs to be attached directly to the `Renderer`, and it can't be easily projected, so it needs to live next to the `ControlCore`. But the `TermControlAutomationPeer` needed the `UiaEngine` to help implement some interfaces.

Now, there's a new layer we've introduced. `InteractivityAutomationPeer` does the `ITextProvider`, `IControlAccessibilityInfo` and the `IUiaEventDispatcher` thing. `TermControlAutomationPeer` now has a 
`InteractivityAutomationPeer` stashed inside itself, so that it can ask the interactivity layer to do the real work. We still need the `TermControlAutomationPeer` though, to be able to attach to the real UI tree.

## Validation Steps Performed

The terminal behaves basically the same as before.

Most importantly, I whipped out Accessibility Insights, and the Terminal looks the same as before.
2021-07-19 11:59:30 -05:00
Carlos Zamora
8947909121 Add a KeyChordListener to the Settings UI (#10652)
## Summary of the Pull Request
Replaces the key chord editor in the actions page with a listener instead of a plain text box.

## References
#6900 - Settings UI Epic

## Detailed Description of the Pull Request / Additional comments
- `Actions` page:
   - Replace `Keys` with `CurrentKeys` for consistency with `Action`/`CurrentAction`
   - `ProposedKeys` is now a `Control::KeyChord`
   - removes key chord validation (now we don't need it)
   - removes accept/cancel shortcuts (nowhere we could use it now)
- `KeyChordListener`:
   - `Keys`: dependency property that hooks us up to a system to the committed setting value
      - this is the key binding view model, which propagates the change to the settings model clone on "accept changes"
   - We bind to `PreviewKeyDown` to intercept the key event _before_ some special key bindings are handled (i.e. "select all" in the text box)
   - `CoreWindow` is used to get the modifier keys because (1) it's easier than updating on each key press and (2) that approach resulted in a strange bug where the <kbd>Alt</kbd> key-up event was not detected
   - `LosingFocus` means that we have completed our operation and want to commit our changes to the key binding view model
   - `KeyDown` does most of the magic of updating `Keys`. We filter out any key chords that could be problematic (i.e. <kbd>Shift</kbd>+<kbd>Tab</kbd> and <kbd>Tab</kbd> for keyboard navigation)

## Validation Steps Performed
- Tested a few key chords:
   - single key: <kbd>X</kbd>
   - key with modifier(s): <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>X</kbd>
   - plain modifier: <kbd>Ctrl</kbd>
   - key that is used by text box: <kbd>Ctrl+A</kbd>
   - key that is used by Windows Terminal: <kbd>Alt</kbd>+<kbd>F4</kbd>
   - key that is taken by Windows OS: <kbd>Windows</kbd>+<kbd>X</kbd>
   - key that is not taken by Windows OS: <kbd>Windows</kbd>+<kbd>Shift</kbd>+<kbd>X</kbd>
- Known issue:
   - global key taken by Windows Terminal: (i.e. quake mode keybinding)
      - Behavior: global key binding executed
      - Expected: key chord recorded

## Demo
![Key Chord Listener Demo](https://user-images.githubusercontent.com/11050425/125538094-08ea4eaa-21eb-4488-a74c-6ce65324cdf1.gif)
2021-07-16 22:11:55 +00:00
PankajBhojwani
293c36d42f Fix unfocused appearance editor not appearing/disappearing correctly (#10675)
## Summary of the Pull Request
Sends the additional xaml notification when the user presses the '+' or delete button for unfocused appearances

## PR Checklist
* [x] Closes #10673
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [x] I work here

## Validation Steps Performed
It works now
2021-07-16 17:18:40 +00:00
Dustin Howett
cf8f411b8f Merge remote-tracking branch 'openconsole/inbox' 2021-07-15 20:56:58 -05:00
Dustin Howett
ee6ca81d70 Migrate OSS up to 84e30bcd3 2021-07-15 14:58:36 -05:00
Dustin Howett
b609266d29 Migrate OSS up to a0527a1db (Codepage Font Change) 2021-07-15 14:57:55 -05:00
Dustin Howett
59e2835736 Migrate OSS up to cdecfcd67 (GDI Surrogates) 2021-07-15 14:57:30 -05:00
Dustin Howett
3bf5436122 Migrate OSS up to d6da6ba35 2021-07-15 14:56:58 -05:00
Dustin Howett
5ea778b2b8 Migrate OSS up to 79a18f082 (A11y Block Range) 2021-07-15 14:56:46 -05:00
Dustin Howett
cabb83db61 Migrate OSS up to d3b9a780d 2021-07-15 14:56:06 -05:00
Dustin Howett
769e910e35 Migrate OSS up to b7fc0f2d4 (PTO/ETO Change) 2021-07-15 14:55:29 -05:00
Dustin Howett
1d02f82ab7 Migrate OSS up to 2770228e0 2021-07-15 14:31:16 -05:00
gabrielconl
84e30bcd3a Fix color picker minimum width (#10663)
Removed custom min width which caused a weird space on the right side. Corner radius and other properties should also work properly now.
2021-07-15 14:39:25 +00:00
Leonard Hecker
f68324cd09 Fix output stuttering using a ticket lock (#10653)
`SRWLOCK`, as used by `std::shared_mutex`, is a inherently unfair mutex
and makes no guarantee whatsoever whether a thread may acquire the lock
in a timely manner. This is problematic for our renderer which relies on
being able to acquire the lock in a timely and predictable manner.
Drawing stalls of up to one minute have been observed in tests.

This issue can be solved with a primitive ticket lock, which is 10x
slower than a `SRWLOCK` but still sufficiently fast for our use case
(10M locks per second per thread). It's likely that any non-trivial lock
duration will diminish the difference to "negligible".

## Validation Steps Performed

* It still blends ✔️
2021-07-14 23:41:22 +00:00
Leonard Hecker
fca87b2bb5 Clean up KeyChordSerialization (#10654)
This commit is a preparation for upcoming changes to
KeyChordSerialization for #7539 and #10203.  It introduces several
string helpers to simplify key chord parsing and get rid of our implicit
dependency on locale sensitive functions, which are known to behave
erratically.

Additionally key chord serialization used to depend on iteration order
of a hashmap which caused different strings to be returned for the same
key chord. This commit fixes the iteration order and will always return
the same string.

## Validation Steps Performed

* Key bindings are correctly parsed ✔️
* Key bindings are correctly serialized 
2021-07-14 21:22:24 +00:00
Dustin Howett
c12835783d version: bump to 1.11 on main 2021-07-14 15:12:24 -05:00
PankajBhojwani
56bbe86f96 Don't override success value when resetting mouse mode in hard reset (#10661)
Quick fix for an error made in #10602 

References #8613
Closes #10658
2021-07-14 16:46:34 +00:00
Dan Thompson
e2005ca5d7 Merged PR 6176782: [Git2Git] Get rid of dead build macros/#defines FE_IME, W32_SB, etc.
Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev 40df712b33a40e76aeeac87f823bbb028d2e3972

Related work items: MSFT-32007459
2021-06-21 10:50:52 -07:00
Dustin Howett
448684a5f4 Merged PR 6164445: Persist inbox conhost; migrate OSS up to 2bd5791fe
Related work items: MSFT-33707417
2021-06-16 19:54:14 +00:00
Dustin Howett
0d3f85de85 Merged PR 6164397: Migrate OSS up to 872309397
Related work items: MSFT-33790379
2021-06-16 19:47:00 +00:00
219 changed files with 7423 additions and 5306 deletions

15
.github/actions/spelling/README.md vendored Normal file
View File

@@ -0,0 +1,15 @@
# check-spelling/check-spelling configuration
File | Purpose | Format | Info
-|-|-|-
[allow/*.txt](allow/) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow)
[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject)
[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes)
[patterns/*.txt](patterns/) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns)
[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
[expect/*.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect)
[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice)
Note: you can replace any of these files with a directory by the same name (minus the suffix)
and then include multiple files inside that directory (with that suffix) to merge multiple files together.

View File

@@ -1,4 +1,4 @@
<!-- markdownlint-disable MD033 MD041 -->
<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details>
<summary>
:pencil2: Contributor please read this
@@ -6,7 +6,7 @@
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
:warning: The command is written for posix shells. You can copy the contents of each `perl` command excluding the outer `'` marks and dropping any `'"`/`"'` quotation mark pairs into a file and then run `perl file.pl` from the root of the repository to run the code. Alternatively, you can manually insert the items...
:warning: The command is written for posix shells. If it doesn't work for you, you can manually _add_ (one word per line) / _remove_ items to `expect.txt` and the `excludes.txt` files.
If the listed items are:
@@ -20,31 +20,29 @@ See the `README.md` in each directory for more information.
:microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink:
<details><summary>:clamp: If you see a bunch of garbage</summary>
If it relates to a ...
<details><summary>well-formed pattern</summary>
<details><summary>If the flagged items are :exploding_head: false positives</summary>
See if there's a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it.
If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).
If not, try writing one and adding it to a `patterns/{file}.txt`.
Please add a file path to the `excludes.txt` file matching the containing file.
Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.
</details>
<details><summary>binary-ish string</summary>
Please add a file path to the `excludes.txt` file instead of just accepting the garbage.
File paths are Perl 5 Regular Expressions - you can [test](
File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).
</details>
* well-formed pattern.
If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
try adding it to the `patterns.txt` file.
Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.
</details>
</details>

View File

@@ -1,7 +1,18 @@
admins
allcolors
Apc
apc
breadcrumb
breadcrumbs
bsd
calt
ccmp
changelog
clickable
clig
CMMI
copyable
cybersecurity
dalet
Dcs
dcs
@@ -11,49 +22,87 @@ downside
downsides
dze
dzhe
EDDB
EDDC
Enum'd
Fitt
formattings
FTCS
ftp
fvar
gantt
gcc
geeksforgeeks
ghe
github
gje
godbolt
hostname
hostnames
https
hyperlink
hyperlinking
hyperlinks
iconify
img
inlined
It'd
kje
libfuzzer
libuv
liga
lje
Llast
llvm
Lmid
locl
lol
lorem
Lorigin
maxed
minimalistic
mkmk
mnt
mru
nje
noreply
ogonek
ok'd
overlined
pipeline
postmodern
ptys
qof
qps
rclt
reimplementation
reserialization
reserialize
reserializes
rlig
runtimes
shcha
slnt
Sos
ssh
timeline
timelines
timestamped
TLDR
tokenizes
tonos
toolset
tshe
ubuntu
uiatextrange
UIs
und
unregister
versioned
vsdevcmd
We'd
wildcards
XBox
YBox
yeru
zhe

View File

@@ -1,28 +1,44 @@
ACCEPTFILES
ACCESSDENIED
acl
aclapi
alignas
alignof
APPLYTOSUBMENUS
appxrecipe
bitfield
bitfields
BUILDBRANCH
BUILDMSG
BUILDNUMBER
BYCOMMAND
BYPOSITION
charconv
CLASSNOTAVAILABLE
CLOSEAPP
cmdletbinding
COLORPROPERTY
colspan
COMDLG
commandlinetoargv
comparand
cstdint
CXICON
CYICON
Dacl
dataobject
dcomp
DERR
dlldata
DNE
DONTADDTORECENT
DWMSBT
DWMWA
DWMWA
DWORDLONG
endfor
ENDSESSION
enumset
environstrings
EXPCMDFLAGS
EXPCMDSTATE
@@ -35,12 +51,16 @@ fullkbd
futex
GETDESKWALLPAPER
GETHIGHCONTRAST
GETMOUSEHOVERTIME
Hashtable
HIGHCONTRASTON
HIGHCONTRASTW
hotkeys
href
hrgn
HTCLOSE
hwinsta
HWINSTA
IActivation
IApp
IAppearance
@@ -57,17 +77,22 @@ IDirect
IExplorer
IFACEMETHOD
IFile
IGraphics
IInheritable
IMap
IMonarch
IObject
iosfwd
IPackage
IPeasant
ISetup
isspace
IStorage
istream
IStringable
ITab
ITaskbar
itow
IUri
IVirtual
KEYSELECT
@@ -76,12 +101,27 @@ llabs
llu
localtime
lround
Lsa
lsass
LSHIFT
LTGRAY
MAINWINDOW
memchr
memicmp
MENUCOMMAND
MENUDATA
MENUINFO
MENUITEMINFOW
mmeapi
MOUSELEAVE
mov
mptt
msappx
MULTIPLEUSE
NCHITTEST
NCLBUTTONDBLCLK
NCMOUSELEAVE
NCMOUSEMOVE
NCRBUTTONDBLCLK
NIF
NIN
@@ -91,6 +131,7 @@ NOCHANGEDIR
NOPROGRESS
NOREDIRECTIONBITMAP
NOREPEAT
NOTIFYBYPOS
NOTIFYICON
NOTIFYICONDATA
ntprivapi
@@ -98,26 +139,36 @@ oaidl
ocidl
ODR
offsetof
ofstream
onefuzz
osver
OSVERSIONINFOEXW
otms
OUTLINETEXTMETRICW
overridable
PACL
PAGESCROLL
PATINVERT
PEXPLICIT
PICKFOLDERS
pmr
ptstr
QUERYENDSESSION
rcx
REGCLS
RETURNCMD
rfind
ROOTOWNER
roundf
RSHIFT
SACL
schandle
semver
serializer
SETVERSION
SHELLEXECUTEINFOW
shobjidl
SHOWHIDE
SHOWMINIMIZED
SHOWTIP
SINGLEUSE
@@ -131,25 +182,44 @@ SRWLOCK
STDCPP
STDMETHOD
strchr
strcpy
streambuf
strtoul
Stubless
Subheader
Subpage
syscall
SYSTEMBACKDROP
TABROW
TASKBARCREATED
TBPF
THEMECHANGED
tlg
TME
tmp
tmpdir
tolower
toupper
TRACKMOUSEEVENT
TTask
TVal
UChar
UFIELD
ULARGE
UOI
UPDATEINIFILE
userenv
USEROBJECTFLAGS
Viewbox
virtualalloc
wcsstr
wcstoui
winmain
winsta
winstamin
wmemcmp
wpc
WSF
wsregex
wwinmain
xchg
@@ -166,6 +236,7 @@ xlocmes
xlocmon
xlocnum
xloctime
XMax
xmemory
XParse
xpath
@@ -174,3 +245,4 @@ xstring
xtree
xutility
YIcon
YMax

View File

@@ -1,3 +1,11 @@
atan
CPrime
HBar
HPrime
isnan
LPrime
LStep
powf
RSub
sqrtf
ULP

View File

@@ -1,5 +1,6 @@
ACLs
ADMINS
advapi
altform
altforms
appendwttlogging
@@ -15,8 +16,10 @@ CPLs
cpptools
cppvsdbg
CPRs
cryptbase
DACL
DACLs
defaultlib
diffs
disposables
dotnetfeed
@@ -24,15 +27,22 @@ DTDs
DWINRT
enablewttlogging
Intelli
IVisual
libucrt
libucrtd
LKG
LOCKFILE
Lxss
mfcribbon
microsoft
microsoftonline
MSAA
msixbundle
MSVC
MSVCP
muxc
netcore
Onefuzz
osgvsowi
PFILETIME
pgc
@@ -43,6 +53,7 @@ powershell
propkey
pscustomobject
QWORD
regedit
robocopy
SACLs
sdkddkver
@@ -56,6 +67,8 @@ systemroot
taskkill
tasklist
tdbuildteamid
ucrt
ucrtd
unvirtualized
VCRT
vcruntime

View File

@@ -1,14 +1,18 @@
Anup
austdi
arkthur
Ballmer
bhoj
Bhojwani
Bluloco
carlos
dhowett
Diviness
dsafa
duhowett
DXP
ekg
eryksun
ethanschoonover
Firefox
Gatta
@@ -20,6 +24,7 @@ Hernan
Howett
Illhardt
iquilezles
italo
jantari
jerrysh
Kaiyu
@@ -31,8 +36,11 @@ Kourosh
kowalczyk
leonmsft
Lepilleur
lhecker
lukesampson
Macbook
Manandhar
masserano
mbadolato
Mehrain
menger
@@ -52,6 +60,7 @@ oldnewthing
opengl
osgwiki
pabhojwa
panos
paulcam
pauldotknopf
PGP
@@ -60,12 +69,17 @@ Rincewind
rprichard
Schoonover
shadertoy
Shomnipotence
simioni
Somuah
sonph
sonpham
stakx
talo
thereses
Walisch
WDX
Wellons
Wirt
Wojciech
zadjii

View File

@@ -0,0 +1,523 @@
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# marker for ignoring a comment to the end of the line
// #no-spell-check.*$
# patch hunk comments
^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
# git index header
index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
# cid urls
(['"])cid:.*?\g{-1}
# data url in parens
\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
# data url in quotes
([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
# data url
data:[-a-zA-Z=;:/0-9+]*,\S*
# mailto urls
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
# magnet urls
magnet:[?=:\w]+
# magnet urls
"magnet:[^"]+"
# obs:
"obs:[^"]*"
# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read
# In this examples content, I'm using a number of different ways to match things to show various approaches
# asciinema
\basciinema\.org/a/[0-9a-zA-Z]+
# apple
\bdeveloper\.apple\.com/[-\w?=/]+
# Apple music
\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+
# appveyor api
\bci\.appveyor\.com/api/projects/status/[0-9a-z]+
# appveyor project
\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+
# Amazon
# Amazon
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
# AWS S3
\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]*
# AWS execute-api
\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b
# AWS ELB
\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b
# AWS SNS
\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]*
# AWS VPC
vpc-\w+
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
# YouTube music
\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*)
# YouTube tag
<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"]
# YouTube image
\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]*
# Google Accounts
\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]*
# Google Analytics
\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
# Google APIs
\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
# Google Storage
\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
# Google Calendar
\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+
\w+\@group\.calendar\.google\.com\b
# Google DataStudio
\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|)
# The leading `/` here is as opposed to the `\b` above
# ... a short way to match `https://` or `http://` since most urls have one of those prefixes
# Google Docs
/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|))
# Google Drive
\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
# Google Groups
\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)*
# Google Maps
\bmaps\.google\.com/maps\?[\w&;=]*
# Google themes
themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
# Google CDN
\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]*
# Goo.gl
/goo\.gl/[a-zA-Z0-9]+
# Google Chrome Store
\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|)
# Google Books
\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]*
# Google Fonts
\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
# Google Forms
\bforms\.gle/\w+
# Google Scholar
\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
# Google Colab Research Drive
\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
# GitHub SHAs (api)
\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
# GitHub SHAs (markdown)
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
# GitHub SHAs
\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
# GitHub wiki
\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
# githubusercontent
/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
# githubassets
\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+)
# gist github
\bgist\.github\.com/[^/\s"]+/[0-9a-f]+
# git.io
\bgit\.io/[0-9a-zA-Z]+
# GitHub JSON
"node_id": "[-a-zA-Z=;:/0-9+]*"
# Contributor
\[[^\]]+\]\(https://github\.com/[^/\s"]+\)
# GHSA
GHSA(?:-[0-9a-z]{4}){3}
# GitLab commit
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
# GitLab merge requests
\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b
# GitLab uploads
\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]*
# GitLab commits
\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
# binanace
accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
# bitbucket diff
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
# bitbucket repositories commits
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
# bitbucket commits
\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
# bit.ly
\bbit\.ly/\w+
# bitrise
\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]*
# bootstrapcdn.com
\bbootstrapcdn\.com/[-./\w]+
# cdn.cloudflare.com
\bcdnjs\.cloudflare\.com/[./\w]+
# circleci
\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+
# gitter
\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+
# gravatar
\bgravatar\.com/avatar/[0-9a-f]+
# ibm
[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]*
# imgur
\bimgur\.com/[^.]+
# Internet Archive
\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*)
# discord
/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,}
# Disqus
\bdisqus\.com/[-\w/%.()!?&=_]*
# medium link
\blink\.medium\.com/[a-zA-Z0-9]+
# medium
\bmedium\.com/\@?[^/\s"]+/[-\w]+
# microsoft
\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]*
# powerbi
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
# vs devops
\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
# microsoft store
\bmicrosoft\.com/store/apps/\w+
# mvnrepository.com
\bmvnrepository\.com/[-0-9a-z./]+
# now.sh
/[0-9a-z-.]+\.now\.sh\b
# oracle
\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]*
# chromatic.com
/\S+.chromatic.com\S*[")]
# codacy
\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+
# compai
\bcompai\.pub/v1/png/[0-9a-f]+
# mailgun api
\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]*
# mailgun
\b[0-9a-z]+.mailgun.org
# /message-id/
/message-id/[-\w@./%]+
# Reddit
\breddit\.com/r/[/\w_]*
# requestb.in
\brequestb\.in/[0-9a-z]+
# sched
\b[a-z0-9]+\.sched\.com\b
# Slack url
slack://[a-zA-Z0-9?&=]+
# Slack
\bslack\.com/[-0-9a-zA-Z/_~?&=.]*
# Slack edge
\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+
# Slack images
\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+
# shields.io
\bshields\.io/[-\w/%?=&.:+;,]*
# stackexchange -- https://stackexchange.com/feeds/sites
\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
# Sentry
[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b
# Twitter markdown
\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
# Twitter hashtag
\btwitter\.com/hashtag/[\w?_=&]*
# Twitter status
\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)
# Twitter profile images
\btwimg\.com/profile_images/[_\w./]*
# Twitter media
\btwimg\.com/media/[-_\w./?=]*
# Twitter link shortened
\bt\.co/\w+
# facebook
\bfburl\.com/[0-9a-z_]+
# facebook CDN
\bfbcdn\.net/[\w/.,]*
# facebook watch
\bfb\.watch/[0-9A-Za-z]+
# dropbox
\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
# ipfs protocol
ipfs://[0-9a-z]*
# ipfs url
/ipfs/[0-9a-z]*
# w3
\bw3\.org/[-0-9a-zA-Z/#.]+
# loom
\bloom\.com/embed/[0-9a-f]+
# regex101
\bregex101\.com/r/[^/\s"]+/\d+
# figma
\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+
# freecodecamp.org
\bfreecodecamp\.org/[-\w/.]+
# image.tmdb.org
\bimage\.tmdb\.org/[/\w.]+
# mermaid
\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+
# Wikipedia
\ben\.wikipedia\.org/wiki/[-\w%.#]+
# gitweb
[^"\s]+/gitweb/\S+;h=[0-9a-f]+
# HyperKitty lists
/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/
# lists
/thread\.html/[^"\s]+
# list-management
\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+
# kubectl.kubernetes.io/last-applied-configuration
"kubectl.kubernetes.io/last-applied-configuration": ".*"
# pgp
\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]*
# Spotify
\bopen\.spotify\.com/embed/playlist/\w+
# Mastodon
\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]*
# scastie
\bscastie\.scala-lang\.org/[^/]+/\w+
# images.unsplash.com
\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+
# pastebin
\bpastebin\.com/[\w/]+
# heroku
\b\w+\.heroku\.com/source/archive/\w+
# quip
\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)?
# badgen.net
\bbadgen\.net/badge/[^")\]'\s]+
# statuspage.io
\w+\.statuspage\.io\b
# media.giphy.com
\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+
# tinyurl
\btinyurl\.com/\w+
# getopts
\bgetopts\s+(?:"[^"]+"|'[^']+')
# ANSI color codes
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
# URL escaped characters
\%[0-9A-F][A-F]
# IPv6
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
# c99 hex digits (not the full format, just one I've seen)
0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
# Punycode
\bxn--[-0-9a-z]+
# sha
sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture
(['"]|&quot;)[0-9a-f]{40,}\g{-1}
# hex runs
\b[0-9a-fA-F]{16,}\b
# hex in url queries
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
# ssh
(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,}
# PGP
\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
# GPG keys
\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b
# Well known gpg keys
.well-known/openpgpkey/[\w./]+
# uuid:
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
# integrity
integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
# https://www.gnu.org/software/groff/manual/groff.html
# man troff content
\\f[BCIPR]
# '
\\\(aq
# .desktop mime types
^MimeTypes?=.*$
# .desktop localized entries
^[A-Z][a-z]+\[[a-z]+\]=.*$
# Localized .desktop content
Name\[[^\]]+\]=.*
# IServiceProvider
\bI(?=(?:[A-Z][a-z]{2,})+\b)
# crypt
"\$2[ayb]\$.{56}"
# scrypt / argon
\$(?:scrypt|argon\d+[di]*)\$\S+
# Input to GitHub JSON
content: "[-a-zA-Z=;:/0-9+]*="
# Python stringprefix / binaryprefix
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
# Regular expressions for (P|p)assword
\([A-Z]\|[a-z]\)[a-z]+
# JavaScript regular expressions
# javascript test regex
/.*/[gim]*\.test\(
# javascript match regex
\.match\(/[^/\s"]*/[gim]*\s*
# javascript match regex
\.match\(/\\[b].*?/[gim]*\s*\)(?:;|$)
# javascript regex
^\s*/\\[b].*/[gim]*\s*(?:\)(?:;|$)|,$)
# javascript replace regex
\.replace\(/[^/\s"]*/[gim]*\s*,
# Go regular expressions
regexp?\.MustCompile\(`[^`]*`\)
# sed regular expressions
sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
# go install
go install(?:\s+[a-z]+\.[-@\w/.]+)+
# kubernetes pod status lists
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
# kubectl - pods in CrashLoopBackOff
\w+-[0-9a-f]+-\w+\s+\d+/\d+\s+CrashLoopBackOff\s+
# kubernetes object suffix
-[0-9a-f]{10}-\w{5}\s
# posthog secrets
posthog\.init\((['"])phc_[^"',]+\g{-1},
# xcode
# xcodeproject scenes
(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}"
# xcode api botches
customObjectInstantitationMethod
# font awesome classes
\.fa-[-a-z0-9]+
# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
## You could manually change `(?i)X...` to use `[Xx]...`
## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
# Lorem
(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
# Non-English
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
# French
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b
# latex
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
# the negative lookahead here is to allow catching 'templatesz' as a misspelling
# but to otherwise recognize a Windows path with \templates\foo.template or similar:
\\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b
# Note that the next example is no longer necessary if you are using
# to match a string starting with a `#`, use a character-class:
[#]backwards
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
# Compiler flags (Scala)
(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# Compiler flags
#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# Compiler flags (linker)
,-B
# curl arguments
\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# set arguments
\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)*
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
# macOS temp folders
/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/

View File

@@ -1,28 +1,39 @@
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
(?:(?i)\.png$)
(?:^|/)(?i)COPYRIGHT
(?:^|/)(?i)LICEN[CS]E
(?:^|/)3rdparty/
(?:^|/)dirs$
(?:^|/)go\.mod$
(?:^|/)go\.sum$
(?:^|/)package-lock\.json$
(?:^|/)package(?:-lock|)\.json$
(?:^|/)sources(?:|\.dep)$
SUMS$
(?:^|/)vendor/
\.a$
\.ai$
\.avi$
\.bmp$
\.bz2$
\.cer$
\.class$
\.crl$
\.crt$
\.csr$
\.dll$
\.docx?$
\.drawio$
\.DS_Store$
\.eot$
\.eps$
\.exe$
\.gif$
\.gitattributes$
\.graffle$
\.gz$
\.icns$
\.ico$
\.jar$
\.jks$
\.jpeg$
\.jpg$
\.key$
@@ -30,28 +41,53 @@ SUMS$
\.lock$
\.map$
\.min\..
\.mod$
\.mp3$
\.mp4$
\.o$
\.ocf$
\.otf$
\.pbxproj$
\.pdf$
\.pem$
\.png$
\.psd$
\.pyc$
\.runsettings$
\.s$
\.sig$
\.so$
\.svg$
\.svgz$
\.svgz?$
\.tar$
\.tgz$
\.tiff?$
\.ttf$
\.vsdx$
\.wav$
\.webm$
\.webp$
\.woff
\.woff2?$
\.xcf$
\.xls
\.xlsx?$
\.xpm$
\.yml$
\.zip$
^\.github/actions/spelling/
^\.github/fabricbot.json$
^\.gitignore$
^\Q.git-blame-ignore-revs\E$
^\Q.github/workflows/spelling.yml\E$
^\Qdoc/reference/windows-terminal-logo.ans\E$
^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$
^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$
^\Qsrc/host/ft_host/chafa.txt\E$
^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$
^\XamlStyler.json$
^build/config/
^consolegit2gitfilters\.json$
^dep/
^doc/reference/master-sequence-list.csv$
@@ -61,12 +97,14 @@ SUMS$
^src/host/runft\.bat$
^src/host/runut\.bat$
^src/interactivity/onecore/BgfxEngine\.
^src/renderer/atlas/
^src/renderer/wddmcon/WddmConRenderer\.
^src/terminal/adapter/ut_adapter/run\.bat$
^src/terminal/parser/delfuzzpayload\.bat$
^src/terminal/parser/ft_fuzzer/run\.bat$
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
^src/terminal/parser/ft_fuzzwrapper/run\.bat$
^src/terminal/parser/ut_parser/Base64Test.cpp$
^src/terminal/parser/ut_parser/run\.bat$
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
^src/tools/lnkd/lnkd\.bat$
@@ -74,6 +112,6 @@ SUMS$
^src/tools/texttests/fira\.txt$
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
^src/types/ut_types/UtilsTests.cpp$
^\.github/actions/spelling/
^\.gitignore$
^\XamlStyler.json$
^tools/ReleaseEngineering/ServicingPipeline.ps1$
ignore$
SUMS$

View File

@@ -5,26 +5,19 @@ AAAAAABBBBBBCCC
AAAAABBBBBBCCC
abcd
abcd
abcde
abcdef
ABCDEFG
ABCDEFGH
ABCDEFGHIJ
abcdefghijk
ABCDEFGHIJKLMNO
abcdefghijklmnop
ABCDEFGHIJKLMNOPQRST
abcdefghijklmnopqrstuvwxyz
ABCG
ABE
abf
BBBBB
BBBBBBBB
BBBBBBBBBBBBBBDDDD
BBBBBCCC
BBBBCCCCC
BBGGRR
CCE
EFG
EFGh
QQQQQQQQQQABCDEFGHIJ
@@ -33,7 +26,6 @@ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
qrstuvwxyz
qwerty
QWERTYUIOP
qwertyuiopasdfg
YYYYYYYDDDDDDDDDDD
ZAAZZ

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,6 @@
http
www
ecma
rapidtables
WCAG
freedesktop
ycombinator
robertelder
kovidgoyal
leonerd
fixterms
winui
appshellintegration
cppreference
mdtauk
gfycat
Guake

View File

@@ -0,0 +1,62 @@
# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere
# \bm_data\b
# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want
# to use this:
#\bfit\(
# s.b. GitHub
\bGithub\b
# s.b. GitLab
\bGitlab\b
# s.b. JavaScript
\bJavascript\b
# s.b. Microsoft
\bMicroSoft\b
# s.b. another
\ban[- ]other\b
# s.b. greater than
\bgreater then\b
# s.b. into
#\sin to\s
# s.b. opt-in
\sopt in\s
# s.b. less than
\bless then\b
# s.b. otherwise
\bother[- ]wise\b
# s.b. nonexistent
\bnon existing\b
\b[Nn]o[nt][- ]existent\b
# s.b. preexisting
[Pp]re[- ]existing
# s.b. preempt
[Pp]re[- ]empt\b
# s.b. preemptively
[Pp]re[- ]emptively
# s.b. reentrancy
[Rr]e[- ]entrancy
# s.b. reentrant
[Rr]e[- ]entrant
# s.b. workaround(s)
#\bwork[- ]arounds?\b
# Reject duplicate words
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s

View File

@@ -1,11 +1,6 @@
https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]*
https://aka\.ms/[-a-zA-Z0-9?&=\/_]*
https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
https://(?:[a-z-]+\.|)github(?:usercontent|)\.com/[-a-zA-Z0-9?%&=_\/.]*
https://www.xfree86.org/[-a-zA-Z0-9?&=\/_#]*
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
https?://\S+
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
@@ -24,3 +19,78 @@ VERIFY_ARE_EQUAL\(L"[^"]+"
std::memory_order_[\w]+
D2DERR_SHADER_COMPILE_FAILED
TIL_FEATURE_[0-9A-Z_]+
vcvars\w*
ROY\sG\.\sBIV
!(?:(?i)ESC)!\[
!(?:(?i)CSI)!(?:\d+(?:;\d+|)m|[ABCDF])
# Python stringprefix / binaryprefix
\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'
# Automatically suggested patterns
# hit-count: 3831 file-count: 582
# IServiceProvider
\bI(?=(?:[A-Z][a-z]{2,})+\b)
# hit-count: 71 file-count: 35
# Compiler flags
(?:^|[\t ,"'`=(])-[D](?=[A-Z]{2,}|[A-Z][a-z])
(?:^|[\t ,"'`=(])-[X](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# hit-count: 41 file-count: 28
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
# hit-count: 20 file-count: 9
# hex runs
\b[0-9a-fA-F]{16,}\b
# hit-count: 10 file-count: 7
# uuid:
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
# hit-count: 4 file-count: 4
# mailto urls
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
# hit-count: 4 file-count: 1
# ANSI color codes
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
# hit-count: 2 file-count: 1
# latex
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
# hit-count: 1 file-count: 1
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
# hit-count: 1 file-count: 1
# Non-English
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
# hit-count: 1 file-count: 1
# French
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b
# acceptable duplicates
# ls directory listings
[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+
# C/idl types + English ...
\s(Guid|long|LONG|that) \g{-1}\s
# javadoc / .net
(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s
# Commit message -- Signed-off-by and friends
^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
# Autogenerated revert commit message
^This reverts commit [0-9a-f]{40}\.$
# vtmode
--vtmode\s+(\w+)\s+\g{-1}\s
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b

View File

@@ -1,22 +1,12 @@
^attache$
^attacher$
^attachers$
^spae$
^spaebook$
^spaecraft$
^spaed$
^spaedom$
^spaeing$
^spaeings$
^spae-man$
^spaeman$
^spaer$
^Spaerobee$
^spaes$
^spaewife$
^spaewoman$
^spaework$
^spaewright$
^wether$
^wethers$
^wetherteg$
benefitting
occurences?
^dependan.*
^oer$
Sorce
^[Ss]pae.*
^untill$
^untilling$
^wether.*

View File

@@ -1,20 +1,134 @@
# spelling.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p
name: Spell checking
# Comment management is handled through a secondary job, for details see:
# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions
#
# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment
# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare)
# it needs `contents: write` in order to add a comment.
#
# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment
# or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment)
# it needs `pull-requests: write` in order to manipulate those comments.
# Updating pull request branches is managed via comment handling.
# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list
#
# These elements work together to make it happen:
#
# `on.issue_comment`
# This event listens to comments by users asking to update the metadata.
#
# `jobs.update`
# This job runs in response to an issue_comment and will push a new commit
# to update the spelling metadata.
#
# `with.experimental_apply_changes_via_bot`
# Tells the action to support and generate messages that enable it
# to make a commit to update the spelling metadata.
#
# `with.ssh_key`
# In order to trigger workflows when the commit is made, you can provide a
# secret (typically, a write-enabled github deploy key).
#
# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key
on:
pull_request_target:
push:
branches:
- "**"
tags-ignore:
- "**"
pull_request_target:
branches:
- "**"
tags-ignore:
- "**"
types:
- 'opened'
- 'reopened'
- 'synchronize'
issue_comment:
types:
- 'created'
jobs:
spelling:
name: Spell checking
permissions:
contents: read
pull-requests: read
actions: read
outputs:
followup: ${{ steps.spelling.outputs.followup }}
runs-on: ubuntu-latest
if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'"
concurrency:
group: spelling-${{ github.event.pull_request.number || github.ref }}
# note: If you use only_check_changed_files, you do not want cancel-in-progress
cancel-in-progress: true
steps:
- name: checkout-merge
if: "contains(github.event_name, 'pull_request')"
uses: actions/checkout@v2
- name: check-spelling
id: spelling
uses: check-spelling/check-spelling@v0.0.21
with:
ref: refs/pull/${{github.event.pull_request.number}}/merge
- name: checkout
if: "!contains(github.event_name, 'pull_request')"
uses: actions/checkout@v2
- uses: check-spelling/check-spelling@v0.0.19
suppress_push_for_open_pull_request: 1
checkout: true
check_file_names: 1
spell_check_this: check-spelling/spell-check-this@prerelease
post_comment: 0
use_magic_file: 1
extra_dictionary_limit: 10
extra_dictionaries:
cspell:software-terms/src/software-terms.txt
cspell:python/src/python/python-lib.txt
cspell:node/node.txt
cspell:cpp/src/stdlib-c.txt
cspell:cpp/src/stdlib-cpp.txt
cspell:fullstack/fullstack.txt
cspell:filetypes/filetypes.txt
cspell:html/html.txt
cspell:cpp/src/compiler-msvc.txt
cspell:python/src/common/extra.txt
cspell:powershell/powershell.txt
cspell:aws/aws.txt
cspell:cpp/src/lang-keywords.txt
cspell:npm/npm.txt
cspell:dotnet/dotnet.txt
cspell:python/src/python/python.txt
cspell:css/css.txt
cspell:cpp/src/stdlib-cmath.txt
check_extra_dictionaries: ''
comment-push:
name: Report (Push)
# If your workflow isn't running on push, you can remove this job
runs-on: ubuntu-latest
needs: spelling
permissions:
contents: write
if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push'
steps:
- name: comment
uses: check-spelling/check-spelling@v0.0.21
with:
checkout: true
spell_check_this: check-spelling/spell-check-this@prerelease
task: ${{ needs.spelling.outputs.followup }}
comment-pr:
name: Report (PR)
# If you workflow isn't running on pull_request*, you can remove this job
runs-on: ubuntu-latest
needs: spelling
permissions:
pull-requests: write
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
uses: check-spelling/check-spelling@v0.0.21
with:
checkout: true
spell_check_this: check-spelling/spell-check-this@prerelease
task: ${{ needs.spelling.outputs.followup }}

View File

@@ -73,6 +73,7 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.unittest", "src\host\ut_lib\host.unittest.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC954747}"
ProjectSection(ProjectDependencies) = postProject
{18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263}
{71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} = {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Unit", "src\host\ut_host\Host.UnitTests.vcxproj", "{531C23E7-4B76-4C08-8AAD-04164CB628C9}"

View File

@@ -1,48 +1,492 @@
# This build should never run as CI or against a pull request.
trigger: none
pr: none
pool:
name: Package ES Standard Build
parameters:
- name: branding
displayName: "Branding (Build Type)"
type: string
default: Release
values:
- Release
- Preview
- name: buildTerminal
displayName: "Build Windows Terminal MSIX"
type: boolean
default: true
- name: buildTerminalVPack
displayName: "Build Windows Terminal VPack"
type: boolean
default: false
- name: buildWPF
displayName: "Build Terminal WPF Control"
type: boolean
default: false
- name: pgoBuildMode
displayName: "PGO Build Mode"
type: string
default: Optimize
values:
- Optimize
- Instrument
- None
- name: buildConfigurations
type: object
default:
- Release
- name: buildPlatforms
type: object
default:
- x64
- x86
- arm64
variables:
baseYearForVersioning: 2019 # Used by build-console-int
versionMajor: 0
versionMinor: 1
TerminalInternalPackageVersion: "0.0.7"
# When we move off PackageES for Versioning, we'll need to switch
# name to this format. For now, though, we need to use DayOfYear.Rev
# to unique our builds, as mandated by PackageES's Setup task.
# name: '$(versionMajor).$(versionMinor).$(DayOfYear)$(Rev:r).0'
#
# Build name/version number above must end with .0 to make the
# store publication machinery happy.
name: 'Terminal_$(date:yyMM).$(date:dd)$(rev:rrr)'
# Build Arguments:
# WindowsTerminalOfficialBuild=[true,false]
# true - this is running on our build agent
# false - running locally
# WindowsTerminalBranding=[Dev,Preview,Release]
# <none> - Development build resources (default)
# Preview - Preview build resources
# Release - regular build resources
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
resources:
repositories:
- repository: self
type: git
ref: main
jobs:
- template: ./templates/build-console-audit-job.yml
parameters:
platform: x64
- job: Build
strategy:
matrix:
${{ each config in parameters.buildConfigurations }}:
${{ each platform in parameters.buildPlatforms }}:
${{ config }}_${{ platform }}:
BuildConfiguration: ${{ config }}
BuildPlatform: ${{ platform }}
displayName: Build
cancelTimeoutInMinutes: 1
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: True
- task: PkgESSetupBuild@10
displayName: Package ES - Setup Build
inputs:
useDfs: false
productName: OpenConsole
disableOutputRedirect: true
- task: PowerShell@2
displayName: Rationalize Build Platform
inputs:
targetType: inline
script: >-
$Arch = "$(BuildPlatform)"
- template: ./templates/build-console-int.yml
parameters:
platform: x64
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
If ($Arch -Eq "x86") { $Arch = "Win32" }
- template: ./templates/build-console-int.yml
parameters:
platform: x86
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}"
- task: NuGetToolInstaller@1
displayName: Use NuGet 5.10
inputs:
versionSpec: 5.10
- task: NuGetCommand@2
displayName: NuGet custom
inputs:
command: custom
selectOrConfig: config
nugetConfigPath: NuGet.Config
arguments: restore OpenConsole.sln -SolutionDirectory $(Build.SourcesDirectory)
- task: UniversalPackages@0
displayName: Download terminal-internal Universal Package
inputs:
feedListDownload: 2b3f8893-a6e8-411f-b197-a9e05576da48
packageListDownload: e82d490c-af86-4733-9dc4-07b772033204
versionListDownload: $(TerminalInternalPackageVersion)
- task: TouchdownBuildTask@1
displayName: Download Localization Files
inputs:
teamId: 7105
authId: $(TouchdownAppId)
authKey: $(TouchdownAppKey)
resourceFilePath: >-
src\cascadia\TerminalApp\Resources\en-US\Resources.resw
- template: ./templates/build-console-int.yml
parameters:
platform: arm64
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
src\cascadia\TerminalControl\Resources\en-US\Resources.resw
- template: ./templates/check-formatting.yml
src\cascadia\TerminalConnection\Resources\en-US\Resources.resw
- template: ./templates/release-sign-and-bundle.yml
src\cascadia\TerminalSettingsModel\Resources\en-US\Resources.resw
src\cascadia\TerminalSettingsEditor\Resources\en-US\Resources.resw
src\cascadia\WindowsTerminalUniversal\Resources\en-US\Resources.resw
src\cascadia\CascadiaPackage\Resources\en-US\Resources.resw
appendRelativeDir: true
localizationTarget: false
pseudoSetting: Included
- task: PowerShell@2
displayName: Move Loc files one level up
inputs:
targetType: inline
script: >-
$Files = Get-ChildItem . -R -Filter 'Resources.resw' | ? FullName -Like '*en-US\*\Resources.resw'
$Files | % { Move-Item -Verbose $_.Directory $_.Directory.Parent.Parent -EA:Ignore }
pwsh: true
- task: PowerShell@2
displayName: Generate NOTICE.html from NOTICE.md
inputs:
filePath: .\build\scripts\Generate-ThirdPartyNotices.ps1
arguments: -MarkdownNoticePath .\NOTICE.md -OutputPath .\src\cascadia\CascadiaPackage\NOTICE.html
pwsh: true
- ${{ if eq(parameters.pgoBuildMode, 'Optimize') }}:
- task: PowerShell@2
displayName: Restore PGO Database
inputs:
filePath: tools/PGODatabase/restore-pgodb.ps1
workingDirectory: $(Build.SourcesDirectory)\tools\PGODatabase
- ${{ if eq(parameters.buildTerminal, true) }}:
- task: VSBuild@1
displayName: Build solution **\OpenConsole.sln
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }} /t:Terminal\CascadiaPackage;Terminal\WindowsTerminalUniversal /p:WindowsTerminalReleaseBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true
maximumCpuCount: true
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: binlog'
condition: failed()
continueOnError: True
inputs:
PathtoPublish: $(Build.SourcesDirectory)\msbuild.binlog
ArtifactName: binlog-$(BuildPlatform)
- ${{ if eq(parameters.pgoBuildMode, 'Optimize') }}:
- task: PowerShell@2
displayName: Validate binaries are optimized
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
inputs:
targetType: inline
script: >-
$Binaries = 'OpenConsole.exe', 'WindowsTerminal.exe', 'TerminalApp.dll', 'TerminalConnection.dll', 'Microsoft.Terminal.Control.dll', 'Microsoft.Terminal.Remoting.dll', 'Microsoft.Terminal.Settings.Editor.dll', 'Microsoft.Terminal.Settings.Model.dll'
foreach ($BinFile in $Binaries) {
& "$(Build.SourcesDirectory)\tools\PGODatabase\verify-pgo.ps1" "$(Build.SourcesDirectory)/src/cascadia/CascadiaPackage/bin/$(BuildPlatform)/$(BuildConfiguration)/$BinFile"
}
- task: PowerShell@2
displayName: Check MSIX for common regressions
inputs:
targetType: inline
script: >-
$Package = Get-ChildItem -Recurse -Filter "CascadiaPackage_*.msix"
.\build\scripts\Test-WindowsTerminalPackage.ps1 -Verbose -Path $Package.FullName
pwsh: true
- ${{ if eq(parameters.buildWPF, true) }}:
- task: VSBuild@1
displayName: Build solution **\OpenConsole.sln for PublicTerminalCore
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }} /p:WindowsTerminalReleaseBuild=true /t:Terminal\wpf\PublicTerminalCore
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
- task: PowerShell@2
displayName: Source Index PDBs
inputs:
filePath: build\scripts\Index-Pdbs.ps1
arguments: -SearchDir '$(Build.SourcesDirectory)' -SourceRoot '$(Build.SourcesDirectory)' -recursive -Verbose -CommitId $(Build.SourceVersion)
errorActionPreference: silentlyContinue
- task: ComponentGovernanceComponentDetection@0
displayName: Component Detection
- task: PowerShell@2
displayName: Run Unit Tests
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
enabled: False
inputs:
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
- task: PowerShell@2
displayName: Run Feature Tests
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
enabled: False
inputs:
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
- ${{ if eq(parameters.buildTerminal, true) }}:
- task: CopyFiles@2
displayName: Copy *.appx/*.msix to Artifacts
inputs:
Contents: >-
**/*.appx
**/*.msix
**/*.appxsym
!**/Microsoft.VCLibs*.appx
TargetFolder: $(Build.ArtifactStagingDirectory)/appx
OverWrite: true
flattenFolders: true
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (appx)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/appx
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)
- ${{ if eq(parameters.buildWPF, true) }}:
- task: CopyFiles@2
displayName: Copy PublicTerminalCore.dll to Artifacts
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
inputs:
Contents: >-
**/PublicTerminalCore.dll
**/api-ms-win-core-synch-l1-2-0.dll
TargetFolder: $(Build.ArtifactStagingDirectory)/wpf
OverWrite: true
flattenFolders: true
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (PublicTerminalCore)
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/wpf
ArtifactName: wpf-dll-$(BuildPlatform)-$(BuildConfiguration)
- task: PublishSymbols@2
displayName: Publish symbols path
continueOnError: True
inputs:
SearchPattern: '**/*.pdb'
IndexSources: false
SymbolServerType: TeamServices
- ${{ if eq(parameters.buildTerminal, true) }}:
- job: BundleAndSign
displayName: Create and sign AppX/MSIX bundles
dependsOn: Build
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: True
- task: PkgESSetupBuild@10
displayName: Package ES - Setup Build
inputs:
useDfs: false
productName: OpenConsole
disableOutputRedirect: true
- task: DownloadBuildArtifacts@0
displayName: Download Artifacts (*.appx, *.msix)
inputs:
downloadType: specific
itemPattern: >-
**/*.msix
**/*.appx
extractTars: false
- task: PowerShell@2
displayName: Create WindowsTerminal*.msixbundle
inputs:
filePath: build\scripts\Create-AppxBundle.ps1
arguments: -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion 0.0.0.0 -OutputPath "$(System.ArtifactsDirectory)\Microsoft.WindowsTerminal_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
- task: PowerShell@2
displayName: Create WindowsTerminalUniversal*.msixbundle
inputs:
filePath: build\scripts\Create-AppxBundle.ps1
arguments: -InputPath "$(System.ArtifactsDirectory)" -ProjectName WindowsTerminalUniversal -BundleVersion $(XES_APPXMANIFESTVERSION) -OutputPath "$(System.ArtifactsDirectory)\Microsoft.WindowsTerminalUniversal_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
- task: EsrpCodeSigning@1
displayName: Submit *.msixbundle to ESRP for code signing
inputs:
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
FolderPath: $(System.ArtifactsDirectory)
Pattern: Microsoft.WindowsTerminal*.msixbundle
UseMinimatch: true
signConfigType: inlineSignParams
inlineOperation: >-
[
{
"KeyCode": "Dynamic",
"CertTemplateName": "WINMSAPP1ST",
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
"OperationCode": "SigntoolSign",
"Parameters": {
"OpusName": "Microsoft",
"OpusInfo": "http://www.microsoft.com",
"FileDigest": "/fd \"SHA256\"",
"TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
},
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "Dynamic",
"CertTemplateName": "WINMSAPP1ST",
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
"OperationCode": "SigntoolVerify",
"Parameters": {},
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: appxbundle-signed'
inputs:
PathtoPublish: $(System.ArtifactsDirectory)
ArtifactName: appxbundle-signed
- ${{ if eq(parameters.buildWPF, true) }}:
- job: PackageAndSignWPF
strategy:
matrix:
${{ each config in parameters.buildConfigurations }}:
${{ config }}:
BuildConfiguration: ${{ config }}
displayName: Create NuGet Package (WPF Terminal Control)
dependsOn: Build
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: True
- task: PkgESSetupBuild@10
displayName: Package ES - Setup Build
inputs:
useDfs: false
productName: OpenConsole
disableOutputRedirect: true
- task: DownloadBuildArtifacts@0
displayName: Download x86 PublicTerminalCore
inputs:
artifactName: wpf-dll-x86-$(BuildConfiguration)
itemPattern: '**/*.dll'
downloadPath: bin\Win32\$(BuildConfiguration)\
extractTars: false
- task: DownloadBuildArtifacts@0
displayName: Download x64 PublicTerminalCore
inputs:
artifactName: wpf-dll-x64-$(BuildConfiguration)
itemPattern: '**/*.dll'
downloadPath: bin\x64\$(BuildConfiguration)\
extractTars: false
- task: PowerShell@2
displayName: Move downloaded artifacts up a level
inputs:
targetType: inline
# Find all artifact files and move them up a directory. Ugh.
script: >-
Get-ChildItem bin -Recurse -Directory -Filter wpf-dll-* | % {
$_ | Get-ChildItem -Recurse -File | % {
Move-Item -Verbose $_.FullName $_.Directory.Parent.FullName
}
}
- task: NuGetToolInstaller@1
displayName: Use NuGet 5.10.0
inputs:
versionSpec: 5.10.0
- task: NuGetCommand@2
displayName: NuGet restore copy
inputs:
selectOrConfig: config
nugetConfigPath: NuGet.Config
- task: VSBuild@1
displayName: Build solution **\OpenConsole.sln for WPF Control
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalReleaseBuild=$(UseReleaseBranding);Version=$(XES_PACKAGEVERSIONNUMBER) /t:Pack
platform: Any CPU
configuration: $(BuildConfiguration)
maximumCpuCount: true
- task: PublishSymbols@2
displayName: Publish symbols path
continueOnError: True
inputs:
SearchPattern: '**/*.pdb'
IndexSources: false
SymbolServerType: TeamServices
SymbolsArtifactName: Symbols_WPF_$(BuildConfiguration)
- task: CopyFiles@2
displayName: Copy *.nupkg to Artifacts
inputs:
Contents: '**/*Wpf*.nupkg'
TargetFolder: $(Build.ArtifactStagingDirectory)/nupkg
OverWrite: true
flattenFolders: true
- task: EsrpCodeSigning@1
displayName: Submit *.nupkg to ESRP for code signing
inputs:
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
FolderPath: $(Build.ArtifactStagingDirectory)/nupkg
Pattern: '*.nupkg'
UseMinimatch: true
signConfigType: inlineSignParams
inlineOperation: >-
[
{
"KeyCode": "CP-401405",
"OperationCode": "NuGetSign",
"Parameters": {},
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "CP-401405",
"OperationCode": "NuGetVerify",
"Parameters": {},
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (nupkg)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)\nupkg
ArtifactName: wpf-nupkg-$(BuildConfiguration)
- ${{ if eq(parameters.buildTerminalVPack, true) }}:
- job: VPack
displayName: Create Windows vPack
dependsOn: BundleAndSign
steps:
- checkout: self
clean: true
submodules: true
- task: PkgESSetupBuild@12
displayName: Package ES - Setup Build
- task: DownloadBuildArtifacts@0
displayName: Download Build Artifacts
inputs:
artifactName: appxbundle-signed
extractTars: false
- task: PowerShell@2
displayName: Rename and stage packages for vpack
inputs:
targetType: inline
script: >-
# Rename to known/fixed name for Windows build system
Get-ChildItem Microsoft.WindowsTerminal_*.msixbundle | Rename-Item -NewName { 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle' }
# Create vpack directory and place item inside
mkdir WindowsTerminal.app
mv Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle .\WindowsTerminal.app\
workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed
- task: PkgESVPack@10
displayName: 'Package ES - VPack'
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
sourceDirectory: $(System.ArtifactsDirectory)\appxbundle-signed\WindowsTerminal.app
description: Windows Terminal pre-install application
pushPkgName: WindowsTerminal.app
owner: condev
...

View File

@@ -1,31 +0,0 @@
parameters:
configuration: 'Release'
platform: ''
additionalBuildArguments: ''
jobs:
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
PGOBuildMode: 'Optimize'
pool:
name: Package ES Lab E
demands:
- msbuild
- visualstudio
- vstest
steps:
- task: PkgESSetupBuild@10
displayName: 'Package ES - Setup Build'
inputs:
useDfs: false
productName: WindowsTerminal
disableOutputRedirect: true
- template: build-console-steps.yml
parameters:
additionalBuildArguments: "/p:XesUseOneStoreVersioning=true;XesBaseYearForStoreVersion=$(baseYearForVersioning) ${{ parameters.additionalBuildArguments }}"

View File

@@ -1,74 +0,0 @@
parameters:
configuration: 'Release'
jobs:
- job: SignDeploy${{ parameters.configuration }}
displayName: Sign and Deploy for ${{ parameters.configuration }}
dependsOn:
- Buildx64AuditMode
- Buildx64Release
- Buildx86Release
- Buildarm64Release
- CodeFormatCheck
condition: |
and
(
in(dependencies.Buildx64AuditMode.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx86Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildarm64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.CodeFormatCheck.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
)
variables:
BuildConfiguration: ${{ parameters.configuration }}
AppxProjectName: CascadiaPackage
AppxBundleName: Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle
pool:
name: Package ES Lab E
steps:
- checkout: self
clean: true
- task: PkgESSetupBuild@10
displayName: 'Package ES - Setup Build'
inputs:
useDfs: false
productName: WindowsTerminal
disableOutputRedirect: true
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
displayName: 'Component Detection'
- task: DownloadBuildArtifacts@0
displayName: Download AppX artifacts
inputs:
artifactName: 'appx-$(BuildConfiguration)'
itemPattern: |
**/*.appx
**/*.msix
downloadPath: '$(Build.ArtifactStagingDirectory)\appx'
- task: PowerShell@2
displayName: 'Create $(AppxBundleName)'
inputs:
targetType: filePath
filePath: '.\build\scripts\Create-AppxBundle.ps1'
arguments: |
-InputPath "$(Build.ArtifactStagingDirectory)\appx" -ProjectName $(AppxProjectName) -BundleVersion 0.0.0.0 -OutputPath "$(Build.ArtifactStagingDirectory)\$(AppxBundleName)"
- task: PkgESCodeSign@10
displayName: 'Package ES - SignConfig.WindowsTerminal.xml'
inputs:
signConfigXml: 'build\config\SignConfig.WindowsTerminal.xml'
inPathRoot: '$(Build.ArtifactStagingDirectory)'
outPathRoot: '$(Build.ArtifactStagingDirectory)\signed'
- task: PublishBuildArtifacts@1
displayName: 'Publish Signed AppX'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)\signed'
ArtifactName: 'appxbundle-signed-$(BuildConfiguration)'

View File

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

View File

@@ -4,13 +4,13 @@
"title": "Microsoft's Windows Terminal Settings Profile Schema",
"definitions": {
"KeyChordSegment": {
"pattern": "^(?<modifier>(?<mod1>ctrl|alt|shift|win)(?:\\+(?<mod2>ctrl|alt|shift|win)(?<!\\k<mod1>))?(?:\\+(?<mod3>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>))?(?:\\+(?<mod4>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>|\\k<mod3>))?\\+)?(?<key>[^\\s+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
"pattern": "^(?:(?:ctrl|alt|shift|win)\\+)*(?:app|backspace|browser_(?:back|forward|refresh|stop|search|favorites|home)|comma|delete|down|end|enter|esc|escape|home|insert|left|menu|minus|pagedown|pageup|period|pgdn|pgup|plus|right|space|tab|up|f(?:1\\d?|2[0-4]?|[3-9])|numpad\\d|numpad_(?:\\d|add|decimal|divide|minus|multiply|period|plus|subtract)|(?:vk|sc)\\((?:[1-9]|1?\\d{2}|2[0-4]\\d|25[0-5])\\)|[^\\s+])(?:\\+(?:ctrl|alt|shift|win))*$",
"type": "string",
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<keyName>\", where each modifier is optional, separated by + symbols, and keyName is either one of the names listed in the table below, or any single key character. The string should be written in full lowercase.\napp, menu\tMENU key\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<KeyName>\", where each modifier is optional. KeyName is either any single key character, an explicit virtual key or scan code in the form vk(nnn) and sc(nnn) respectively, or one of the special names listed at https://docs.microsoft.com/en-us/windows/terminal/customize-settings/actions#accepted-modifiers-and-keys"
},
"Color": {
"default": "#",
"pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
"pattern": "^#[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{3})?$",
"type": "string",
"format": "color"
},
@@ -238,6 +238,7 @@
"identifyWindow",
"identifyWindows",
"moveFocus",
"movePane",
"moveTab",
"newTab",
"newWindow",
@@ -514,6 +515,23 @@
],
"required": [ "direction" ]
},
"MovePaneAction": {
"description": "Arguments corresponding to a Move Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "movePane" },
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
"description": "The direction to move the focus pane in, swapping panes. Direction can be 'previous' to swap with the most recently used pane."
}
}
}
],
"required": [ "direction" ]
},
"ResizePaneAction": {
"description": "Arguments corresponding to a Resize Pane Action",
"allOf": [
@@ -596,7 +614,7 @@
"defaultsFile",
"allFiles",
"settingsUI"
]
}
}
@@ -952,6 +970,7 @@
{ "$ref": "#/definitions/NewTabAction" },
{ "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/MovePaneAction" },
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SendInputAction" },
{ "$ref": "#/definitions/SplitPaneAction" },
@@ -1116,6 +1135,10 @@
"description": "When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one.",
"type": "boolean"
},
"experimental.input.forceVT": {
"description": "Force the terminal to use the legacy input encoding. Certain keys in some applications may stop working when enabling this setting.",
"type": "boolean"
},
"initialCols": {
"default": 120,
"description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",

View File

@@ -5,7 +5,7 @@
#include "MyPage.h"
#include <LibraryResources.h>
#include "MyPage.g.cpp"
#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h"
#include "MySettings.h"
using namespace std::chrono_literals;
using namespace winrt::Microsoft::Terminal;
@@ -26,17 +26,24 @@ namespace winrt::SampleApp::implementation
void MyPage::Create()
{
TerminalConnection::EchoConnection conn{};
auto settings = winrt::make_self<ControlUnitTests::MockControlSettings>();
auto settings = winrt::make_self<implementation::MySettings>();
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
winrt::hstring{},
L"",
nullptr,
32,
80,
winrt::guid()) };
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
TerminalConnection::ConnectionInformation connectInfo{ myClass, connectionSettings };
TerminalConnection::ITerminalConnection conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectInfo) };
Control::TermControl control{ *settings, conn };
InProcContent().Children().Append(control);
// Once the control loads (and not before that), write some text for debugging:
control.Initialized([conn](auto&&, auto&&) {
conn.WriteInput(L"This TermControl is hosted in-proc...");
});
}
// Method Description:

View File

@@ -19,10 +19,15 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="TabRow"
Grid.Row="0">
&quot;Tabs&quot;
</Button>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="GuidInput"
Width="400"
PlaceholderText="{}{guid here}" />
<Button Grid.Row="0">
Create
</Button>
</StackPanel>
<Grid x:Name="TabContent"
Grid.Row="1"

View File

@@ -4,6 +4,8 @@ Licensed under the MIT license.
--*/
#pragma once
#include "../../inc/cppwinrt_utils.h"
#include "../types/inc/colorTable.hpp"
#include <DefaultSettings.h>
#include <conattrs.hpp>
#include "MySettings.g.h"
@@ -12,9 +14,6 @@ namespace winrt::SampleApp::implementation
{
struct MySettings : MySettingsT<MySettings>
{
public:
MySettings() = default;
// --------------------------- Core Settings ---------------------------
// All of these settings are defined in ICoreSettings.
@@ -88,6 +87,14 @@ namespace winrt::SampleApp::implementation
winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); }
std::array<winrt::Microsoft::Terminal::Core::Color, 16> ColorTable() { return _ColorTable; }
void ColorTable(std::array<winrt::Microsoft::Terminal::Core::Color, 16> /*colors*/) {}
MySettings()
{
const auto campbellSpan = ::Microsoft::Console::Utils::CampbellColorTable();
std::transform(campbellSpan.begin(), campbellSpan.end(), _ColorTable.begin(), [](auto&& color) {
return static_cast<winrt::Microsoft::Terminal::Core::Color>(til::color{ color });
});
}
};
}

View File

@@ -48,6 +48,10 @@
<Private>true</Private>
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
</ItemGroup>

View File

@@ -31,6 +31,8 @@ public:
using const_iterator = rle_vector::const_iterator;
ATTR_ROW(uint16_t width, TextAttribute attr);
ATTR_ROW(rle_vector&& v) :
_data(std::move(v)) {}
~ATTR_ROW() = default;
@@ -57,11 +59,11 @@ public:
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
friend class ROW;
rle_vector _data;
private:
void Reset(const TextAttribute attr);
rle_vector _data;
#ifdef UNIT_TESTING
friend class CommonState;
#endif

View File

@@ -1,281 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "CharRow.hpp"
#include "unicode.hpp"
#include "Row.hpp"
// Routine Description:
// - constructor
// Arguments:
// - rowWidth - the size (in wchar_t) of the char and attribute rows
// - pParent - the parent ROW
// Return Value:
// - instantiated object
// Note: will through if unable to allocate char/attribute buffers
#pragma warning(push)
#pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build.
CharRow::CharRow(size_t rowWidth, ROW* const pParent) noexcept :
_data(rowWidth, value_type()),
_pParent{ FAIL_FAST_IF_NULL(pParent) }
{
}
#pragma warning(pop)
// Routine Description:
// - gets the size of the row, in glyph cells
// Arguments:
// - <none>
// Return Value:
// - the size of the row
size_t CharRow::size() const noexcept
{
return _data.size();
}
// Routine Description:
// - Sets all properties of the CharRowBase to default values
// Arguments:
// - sRowWidth - The width of the row.
// Return Value:
// - <none>
void CharRow::Reset() noexcept
{
for (auto& cell : _data)
{
cell.Reset();
}
}
// Routine Description:
// - resizes the width of the CharRowBase
// Arguments:
// - newSize - the new width of the character and attributes rows
// Return Value:
// - S_OK on success, otherwise relevant error code
[[nodiscard]] HRESULT CharRow::Resize(const size_t newSize) noexcept
{
try
{
const value_type insertVals;
_data.resize(newSize, insertVals);
}
CATCH_RETURN();
return S_OK;
}
typename CharRow::iterator CharRow::begin() noexcept
{
return _data.begin();
}
typename CharRow::const_iterator CharRow::cbegin() const noexcept
{
return _data.cbegin();
}
typename CharRow::iterator CharRow::end() noexcept
{
return _data.end();
}
typename CharRow::const_iterator CharRow::cend() const noexcept
{
return _data.cend();
}
// Routine Description:
// - Inspects the current internal string to find the left edge of it
// Arguments:
// - <none>
// Return Value:
// - The calculated left boundary of the internal string.
size_t CharRow::MeasureLeft() const noexcept
{
const_iterator it = _data.cbegin();
while (it != _data.cend() && it->IsSpace())
{
++it;
}
return it - _data.cbegin();
}
// Routine Description:
// - Inspects the current internal string to find the right edge of it
// Arguments:
// - <none>
// Return Value:
// - The calculated right boundary of the internal string.
size_t CharRow::MeasureRight() const
{
const_reverse_iterator it = _data.crbegin();
while (it != _data.crend() && it->IsSpace())
{
++it;
}
return _data.crend() - it;
}
void CharRow::ClearCell(const size_t column)
{
_data.at(column).Reset();
}
// Routine Description:
// - Tells you whether or not this row contains any valid text.
// Arguments:
// - <none>
// Return Value:
// - True if there is valid text in this row. False otherwise.
bool CharRow::ContainsText() const noexcept
{
for (const value_type& cell : _data)
{
if (!cell.IsSpace())
{
return true;
}
}
return false;
}
// Routine Description:
// - gets the attribute at the specified column
// Arguments:
// - column - the column to get the attribute for
// Return Value:
// - the attribute
// Note: will throw exception if column is out of bounds
const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const
{
return _data.at(column).DbcsAttr();
}
// Routine Description:
// - gets the attribute at the specified column
// Arguments:
// - column - the column to get the attribute for
// Return Value:
// - the attribute
// Note: will throw exception if column is out of bounds
DbcsAttribute& CharRow::DbcsAttrAt(const size_t column)
{
return _data.at(column).DbcsAttr();
}
// Routine Description:
// - resets text data at column
// Arguments:
// - column - column index to clear text data from
// Return Value:
// - <none>
// Note: will throw exception if column is out of bounds
void CharRow::ClearGlyph(const size_t column)
{
_data.at(column).EraseChars();
}
// Routine Description:
// - returns text data at column as a const reference.
// Arguments:
// - column - column to get text data for
// Return Value:
// - text data at column
// - Note: will throw exception if column is out of bounds
const CharRow::reference CharRow::GlyphAt(const size_t column) const
{
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
return { const_cast<CharRow&>(*this), column };
}
// Routine Description:
// - returns text data at column as a reference.
// Arguments:
// - column - column to get text data for
// Return Value:
// - text data at column
// - Note: will throw exception if column is out of bounds
CharRow::reference CharRow::GlyphAt(const size_t column)
{
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
return { *this, column };
}
std::wstring CharRow::GetText() const
{
std::wstring wstr;
wstr.reserve(_data.size());
for (size_t i = 0; i < _data.size(); ++i)
{
const auto glyph = GlyphAt(i);
if (!DbcsAttrAt(i).IsTrailing())
{
for (const auto wch : glyph)
{
wstr.push_back(wch);
}
}
}
return wstr;
}
// Method Description:
// - get delimiter class for a position in the char row
// - used for double click selection and uia word navigation
// Arguments:
// - column: column to get text data for
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
// Return Value:
// - the delimiter class for the given char
const DelimiterClass CharRow::DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
{
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
const auto glyph = *GlyphAt(column).begin();
if (glyph <= UNICODE_SPACE)
{
return DelimiterClass::ControlChar;
}
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
}
UnicodeStorage& CharRow::GetUnicodeStorage() noexcept
{
return _pParent->GetUnicodeStorage();
}
const UnicodeStorage& CharRow::GetUnicodeStorage() const noexcept
{
return _pParent->GetUnicodeStorage();
}
// Routine Description:
// - calculates the key used by the given column of the char row to store glyph data in UnicodeStorage
// Arguments:
// - column - the column to generate the key for
// Return Value:
// - the COORD key for data access from UnicodeStorage for the column
COORD CharRow::GetStorageKey(const size_t column) const noexcept
{
return { gsl::narrow<SHORT>(column), _pParent->GetId() };
}
// Routine Description:
// - Updates the pointer to the parent row (which might change if we shuffle the rows around)
// Arguments:
// - pParent - Pointer to the parent row
void CharRow::UpdateParent(ROW* const pParent)
{
_pParent = FAIL_FAST_IF_NULL(pParent);
}

View File

@@ -1,115 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- CharRow.hpp
Abstract:
- contains data structure for UCS2 encoded character data of a row
Author(s):
- Michael Niksa (miniksa) 10-Apr-2014
- Paul Campbell (paulcam) 10-Apr-2014
Revision History:
- From components of output.h/.c
by Therese Stowell (ThereseS) 1990-1991
- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017)
--*/
#pragma once
#include "DbcsAttribute.hpp"
#include "CharRowCellReference.hpp"
#include "CharRowCell.hpp"
#include "UnicodeStorage.hpp"
class ROW;
enum class DelimiterClass
{
ControlChar,
DelimiterChar,
RegularChar
};
// the characters of one row of screen buffer
// we keep the following values so that we don't write
// more pixels to the screen than we have to:
// left is initialized to screenbuffer width. right is
// initialized to zero.
//
// [ foo.bar 12-12-61 ]
// ^ ^ ^ ^
// | | | |
// Chars Left Right end of Chars buffer
class CharRow final
{
public:
using glyph_type = typename wchar_t;
using value_type = typename CharRowCell;
using iterator = typename boost::container::small_vector_base<value_type>::iterator;
using const_iterator = typename boost::container::small_vector_base<value_type>::const_iterator;
using const_reverse_iterator = typename boost::container::small_vector_base<value_type>::const_reverse_iterator;
using reference = typename CharRowCellReference;
CharRow(size_t rowWidth, ROW* const pParent) noexcept;
size_t size() const noexcept;
[[nodiscard]] HRESULT Resize(const size_t newSize) noexcept;
size_t MeasureLeft() const noexcept;
size_t MeasureRight() const;
bool ContainsText() const noexcept;
const DbcsAttribute& DbcsAttrAt(const size_t column) const;
DbcsAttribute& DbcsAttrAt(const size_t column);
void ClearGlyph(const size_t column);
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const;
// working with glyphs
const reference GlyphAt(const size_t column) const;
reference GlyphAt(const size_t column);
// iterators
iterator begin() noexcept;
const_iterator cbegin() const noexcept;
const_iterator begin() const noexcept { return cbegin(); }
iterator end() noexcept;
const_iterator cend() const noexcept;
const_iterator end() const noexcept { return cend(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
COORD GetStorageKey(const size_t column) const noexcept;
void UpdateParent(ROW* const pParent);
friend CharRowCellReference;
friend class ROW;
private:
void Reset() noexcept;
void ClearCell(const size_t column);
std::wstring GetText() const;
protected:
// storage for glyph data and dbcs attributes
boost::container::small_vector<value_type, 120> _data;
// ROW that this CharRow belongs to
ROW* _pParent;
};
template<typename InputIt1, typename InputIt2>
void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt)
{
std::transform(startChars,
endChars,
startAttrs,
outIt,
[](const wchar_t wch, const DbcsAttribute attr) {
return CharRow::value_type{ wch, attr };
});
}

View File

@@ -1,73 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "CharRowCell.hpp"
#include "unicode.hpp"
// default glyph value, used for resetting the character data portion of a cell
static constexpr wchar_t DefaultValue = UNICODE_SPACE;
// Routine Description:
// - "erases" the glyph. really sets it back to the default "empty" value
void CharRowCell::EraseChars() noexcept
{
if (_attr.IsGlyphStored())
{
_attr.SetGlyphStored(false);
}
_wch = DefaultValue;
}
// Routine Description:
// - resets this object back to the defaults it would have from the default constructor
void CharRowCell::Reset() noexcept
{
_attr.Reset();
_wch = DefaultValue;
}
// Routine Description:
// - checks if cell contains a space glyph
// Return Value:
// - true if cell contains a space glyph, false otherwise
bool CharRowCell::IsSpace() const noexcept
{
return !_attr.IsGlyphStored() && _wch == UNICODE_SPACE;
}
// Routine Description:
// - Access the DbcsAttribute for the cell
// Return Value:
// - ref to the cells' DbcsAttribute
DbcsAttribute& CharRowCell::DbcsAttr() noexcept
{
return _attr;
}
// Routine Description:
// - Access the DbcsAttribute for the cell
// Return Value:
// - ref to the cells' DbcsAttribute
const DbcsAttribute& CharRowCell::DbcsAttr() const noexcept
{
return _attr;
}
// Routine Description:
// - Access the cell's wchar field. this does not access any char data through UnicodeStorage.
// Return Value:
// - the cell's wchar field
wchar_t& CharRowCell::Char() noexcept
{
return _wch;
}
// Routine Description:
// - Access the cell's wchar field. this does not access any char data through UnicodeStorage.
// Return Value:
// - the cell's wchar field
const wchar_t& CharRowCell::Char() const noexcept
{
return _wch;
}

View File

@@ -1,65 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- CharRowCell.hpp
Abstract:
- data structure for one cell of a char row. contains the char data for one
coordinate position in the output buffer (leading/trailing information and
the char itself.
Author(s):
- Austin Diviness (AustDi) 02-May-2018
--*/
#pragma once
#include "DbcsAttribute.hpp"
#include "unicode.hpp"
#if (defined(_M_IX86) || defined(_M_AMD64))
// currently CharRowCell's fields use 3 bytes of memory, leaving the 4th byte in unused. this leads
// to a rather large amount of useless memory allocated. so instead, pack CharRowCell by bytes instead of words.
#pragma pack(push, 1)
#endif
class CharRowCell final
{
public:
CharRowCell() noexcept = default;
CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept
:
_wch(wch),
_attr(attr)
{
}
void EraseChars() noexcept;
void Reset() noexcept;
bool IsSpace() const noexcept;
DbcsAttribute& DbcsAttr() noexcept;
const DbcsAttribute& DbcsAttr() const noexcept;
wchar_t& Char() noexcept;
const wchar_t& Char() const noexcept;
friend constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept;
private:
wchar_t _wch{ UNICODE_SPACE };
DbcsAttribute _attr{};
};
#if (defined(_M_IX86) || defined(_M_AMD64))
#pragma pack(pop)
#endif
constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept
{
return (a._wch == b._wch &&
a._attr == b._attr);
}

View File

@@ -1,136 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "UnicodeStorage.hpp"
#include "CharRow.hpp"
// Routine Description:
// - assignment operator. will store extended glyph data in a separate storage location
// Arguments:
// - chars - the glyph data to store
void CharRowCellReference::operator=(const std::wstring_view chars)
{
THROW_HR_IF(E_INVALIDARG, chars.empty());
if (chars.size() == 1)
{
_cellData().Char() = chars.front();
_cellData().DbcsAttr().SetGlyphStored(false);
}
else
{
auto& storage = _parent.GetUnicodeStorage();
const auto key = _parent.GetStorageKey(_index);
storage.StoreGlyph(key, { chars.cbegin(), chars.cend() });
_cellData().DbcsAttr().SetGlyphStored(true);
}
}
// Routine Description:
// - implicit conversion to vector<wchar_t> operator.
// Return Value:
// - std::vector<wchar_t> of the glyph data in the referenced cell
CharRowCellReference::operator std::wstring_view() const
{
return _glyphData();
}
// Routine Description:
// - The CharRowCell this object "references"
// Return Value:
// - ref to the CharRowCell
CharRowCell& CharRowCellReference::_cellData()
{
return _parent._data.at(_index);
}
// Routine Description:
// - The CharRowCell this object "references"
// Return Value:
// - ref to the CharRowCell
const CharRowCell& CharRowCellReference::_cellData() const
{
return _parent._data.at(_index);
}
// Routine Description:
// - the glyph data of the referenced cell
// Return Value:
// - the glyph data
std::wstring_view CharRowCellReference::_glyphData() const
{
if (_cellData().DbcsAttr().IsGlyphStored())
{
const auto& text = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index));
return { text.data(), text.size() };
}
else
{
return { &_cellData().Char(), 1 };
}
}
// Routine Description:
// - gets read-only iterator to the beginning of the glyph data
// Return Value:
// - iterator of the glyph data
CharRowCellReference::const_iterator CharRowCellReference::begin() const
{
if (_cellData().DbcsAttr().IsGlyphStored())
{
return _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index)).data();
}
else
{
return &_cellData().Char();
}
}
// Routine Description:
// - get read-only iterator to the end of the glyph data
// Return Value:
// - end iterator of the glyph data
#pragma warning(push)
#pragma warning(disable : 26481)
// TODO GH 2672: eliminate using pointers raw as begin/end markers in this class
CharRowCellReference::const_iterator CharRowCellReference::end() const
{
if (_cellData().DbcsAttr().IsGlyphStored())
{
const auto& chars = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index));
return chars.data() + chars.size();
}
else
{
return &_cellData().Char() + 1;
}
}
#pragma warning(pop)
bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph)
{
const DbcsAttribute& dbcsAttr = ref._cellData().DbcsAttr();
if (glyph.size() == 1 && dbcsAttr.IsGlyphStored())
{
return false;
}
else if (glyph.size() > 1 && !dbcsAttr.IsGlyphStored())
{
return false;
}
else if (glyph.size() == 1 && !dbcsAttr.IsGlyphStored())
{
return ref._cellData().Char() == glyph.front();
}
else
{
const auto& chars = ref._parent.GetUnicodeStorage().GetText(ref._parent.GetStorageKey(ref._index));
return chars == glyph;
}
}
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref)
{
return ref == glyph;
}

View File

@@ -1,63 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- CharRowCellReference.hpp
Abstract:
- reference class for the glyph data of a char row cell
Author(s):
- Austin Diviness (AustDi) 02-May-2018
--*/
#pragma once
#include "DbcsAttribute.hpp"
#include "CharRowCell.hpp"
#include <utility>
class CharRow;
class CharRowCellReference final
{
public:
using const_iterator = const wchar_t*;
CharRowCellReference(CharRow& parent, const size_t index) noexcept :
_parent{ parent },
_index{ index }
{
}
~CharRowCellReference() = default;
CharRowCellReference(const CharRowCellReference&) noexcept = default;
CharRowCellReference(CharRowCellReference&&) noexcept = default;
void operator=(const CharRowCellReference&) = delete;
void operator=(CharRowCellReference&&) = delete;
void operator=(const std::wstring_view chars);
operator std::wstring_view() const;
const_iterator begin() const;
const_iterator end() const;
friend bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
friend bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);
private:
// what char row the object belongs to
CharRow& _parent;
// the index of the cell in the parent char row
const size_t _index;
CharRowCell& _cellData();
const CharRowCell& _cellData() const;
std::wstring_view _glyphData() const;
};
bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);

View File

@@ -27,14 +27,12 @@ public:
};
DbcsAttribute() noexcept :
_attribute{ Attribute::Single },
_glyphStored{ false }
_attribute{ Attribute::Single }
{
}
DbcsAttribute(const Attribute attribute) noexcept :
_attribute{ attribute },
_glyphStored{ false }
_attribute{ attribute }
{
}
@@ -58,16 +56,6 @@ public:
return IsLeading() || IsTrailing();
}
constexpr bool IsGlyphStored() const noexcept
{
return _glyphStored;
}
void SetGlyphStored(const bool stored) noexcept
{
_glyphStored = stored;
}
void SetSingle() noexcept
{
_attribute = Attribute::Single;
@@ -86,7 +74,6 @@ public:
void Reset() noexcept
{
SetSingle();
SetGlyphStored(false);
}
WORD GeneratePublicApiAttributeFormat() const noexcept
@@ -127,7 +114,6 @@ public:
private:
Attribute _attribute : 2;
bool _glyphStored : 1;
#ifdef UNIT_TESTING
friend class TextBufferTests;

View File

@@ -1,546 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "OutputCellIterator.hpp"
#include "../../types/inc/convert.hpp"
#include "../../types/inc/Utf16Parser.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include "../../inc/conattrs.hpp"
static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR };
// Routine Description:
// - This is a fill-mode iterator for one particular wchar. It will repeat forever if fillLimit is 0.
// Arguments:
// - wch - The character to use for filling
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(wch)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is a fill-mode iterator for one particular color. It will repeat forever if fillLimit is 0.
// Arguments:
// - attr - The color attribute to use for filling
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(attr)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is a fill-mode iterator for one particular character and color. It will repeat forever if fillLimit is 0.
// Arguments:
// - wch - The character to use for filling
// - attr - The color attribute to use for filling
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(wch, attr)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is a fill-mode iterator for one particular CHAR_INFO. It will repeat forever if fillLimit is 0.
// Arguments:
// - charInfo - The legacy character and color data to use for filling (uses Unicode portion of text data)
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(charInfo)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is an iterator over a range of text only. No color data will be modified as the text is inserted.
// Arguments:
// - utf16Text - UTF-16 text range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
_mode(Mode::LooseTextOnly),
_currentView(s_GenerateView(utf16Text)),
_run(utf16Text),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over a range text that will apply the same color to every position.
// Arguments:
// - utf16Text - UTF-16 text range
// - attribute - Color to apply over the entire range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute) :
_mode(Mode::Loose),
_currentView(s_GenerateView(utf16Text, attribute)),
_run(utf16Text),
_attr(attribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over legacy colors only. The text is not modified.
// Arguments:
// - legacyAttrs - One legacy color item per cell
OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs) noexcept :
_mode(Mode::LegacyAttr),
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
_run(legacyAttrs),
_attr(InvalidTextAttribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
// Arguments:
// - charInfos - Multiple cell with unicode text and legacy color data.
OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept :
_mode(Mode::CharInfo),
_currentView(s_GenerateView(til::at(charInfos, 0))),
_run(charInfos),
_attr(InvalidTextAttribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over existing OutputCells with full text and color data.
// Arguments:
// - cells - Multiple cells in a run
OutputCellIterator::OutputCellIterator(const gsl::span<const OutputCell> cells) :
_mode(Mode::Cell),
_currentView(s_GenerateView(til::at(cells, 0))),
_run(cells),
_attr(InvalidTextAttribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - Specifies whether this iterator is valid for dereferencing (still valid underlying data)
// Return Value:
// - True if the views on dereference are valid. False if it shouldn't be dereferenced.
OutputCellIterator::operator bool() const noexcept
{
try
{
switch (_mode)
{
case Mode::Loose:
case Mode::LooseTextOnly:
{
// In lieu of using start and end, this custom iterator type simply becomes bool false
// when we run out of items to iterate over.
return _pos < std::get<std::wstring_view>(_run).length();
}
case Mode::Fill:
{
if (_fillLimit > 0)
{
return _pos < _fillLimit;
}
return true;
}
case Mode::Cell:
{
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
}
case Mode::CharInfo:
{
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
}
case Mode::LegacyAttr:
{
return _pos < std::get<gsl::span<const WORD>>(_run).size();
}
default:
FAIL_FAST_HR(E_NOTIMPL);
}
}
CATCH_FAIL_FAST();
}
// Routine Description:
// - Advances the iterator one position over the underlying data source.
// Return Value:
// - Reference to self after advancement.
OutputCellIterator& OutputCellIterator::operator++()
{
// Keep track of total distance moved (cells filled)
_distance++;
switch (_mode)
{
case Mode::Loose:
{
if (!_TryMoveTrailing())
{
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
_pos += _currentView.Chars().size();
if (operator bool())
{
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos), _attr);
}
}
break;
}
case Mode::LooseTextOnly:
{
if (!_TryMoveTrailing())
{
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
_pos += _currentView.Chars().size();
if (operator bool())
{
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos));
}
}
break;
}
case Mode::Fill:
{
if (!_TryMoveTrailing())
{
if (_currentView.DbcsAttr().IsTrailing())
{
auto dbcsAttr = _currentView.DbcsAttr();
dbcsAttr.SetLeading();
_currentView = OutputCellView(_currentView.Chars(),
dbcsAttr,
_currentView.TextAttr(),
_currentView.TextAttrBehavior());
}
if (_fillLimit > 0)
{
// We walk forward by one because we fill with the same cell over and over no matter what
_pos++;
}
}
break;
}
case Mode::Cell:
{
// Walk forward by one because cells are assumed to be in the form they needed to be
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
}
break;
}
case Mode::CharInfo:
{
// Walk forward by one because charinfos are just the legacy version of cells and prealigned to columns
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
}
break;
}
case Mode::LegacyAttr:
{
// Walk forward by one because color attributes apply cell by cell (no complex text information)
_pos++;
if (operator bool())
{
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
}
break;
}
default:
FAIL_FAST_HR(E_NOTIMPL);
}
return (*this);
}
// Routine Description:
// - Advances the iterator one position over the underlying data source.
// Return Value:
// - Reference to self after advancement.
OutputCellIterator OutputCellIterator::operator++(int)
{
auto temp(*this);
operator++();
return temp;
}
// Routine Description:
// - Reference the view to fully-formed output cell data representing the underlying data source.
// Return Value:
// - Reference to the view
const OutputCellView& OutputCellIterator::operator*() const noexcept
{
return _currentView;
}
// Routine Description:
// - Get pointer to the view to fully-formed output cell data representing the underlying data source.
// Return Value:
// - Pointer to the view
const OutputCellView* OutputCellIterator::operator->() const noexcept
{
return &_currentView;
}
// Routine Description:
// - Checks the current view. If it is a leading half, it updates the current
// view to the trailing half of the same glyph.
// - This helps us to draw glyphs that are two columns wide by "doubling"
// the view that is returned so it will consume two cells.
// Return Value:
// - True if we just turned a lead half into a trailing half (and caller doesn't
// need to further update the view).
// - False if this wasn't applicable and the caller should update the view.
bool OutputCellIterator::_TryMoveTrailing() noexcept
{
if (_currentView.DbcsAttr().IsLeading())
{
auto dbcsAttr = _currentView.DbcsAttr();
dbcsAttr.SetTrailing();
_currentView = OutputCellView(_currentView.Chars(),
dbcsAttr,
_currentView.TextAttr(),
_currentView.TextAttrBehavior());
return true;
}
else
{
return false;
}
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and specify that the attributes shouldn't be changed.
// Arguments:
// - view - View representing characters corresponding to a single glyph
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
{
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - view - View representing characters corresponding to a single glyph
// - attr - Color attributes to apply to the text
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr)
{
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - view - View representing characters corresponding to a single glyph
// - attr - Color attributes to apply to the text
// - behavior - Behavior of the given text attribute (used when writing)
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior)
{
const auto glyph = Utf16Parser::ParseNext(view);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(glyph))
{
dbcsAttr.SetLeading();
}
return OutputCellView(glyph, dbcsAttr, attr, behavior);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept
{
const auto glyph = std::wstring_view(&wch, 1);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(wch))
{
dbcsAttr.SetLeading();
}
return OutputCellView(glyph, dbcsAttr, InvalidTextAttribute, TextAttributeBehavior::Current);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - attr - View representing a single color
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noexcept
{
return OutputCellView({}, {}, attr, TextAttributeBehavior::StoredOnly);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
// - attr - View representing a single color
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept
{
const auto glyph = std::wstring_view(&wch, 1);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(wch))
{
dbcsAttr.SetLeading();
}
return OutputCellView(glyph, dbcsAttr, attr, TextAttributeBehavior::Stored);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - legacyAttr - View representing a single legacy color
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept
{
WORD cleanAttr = legacyAttr;
WI_ClearAllFlags(cleanAttr, COMMON_LVB_SBCSDBCS); // don't use legacy lead/trailing byte flags for colors
const TextAttribute attr(cleanAttr);
return s_GenerateView(attr);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - charInfo - character and attribute pair representing a single cell
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noexcept
{
const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1);
DbcsAttribute dbcsAttr;
if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_LEADING_BYTE))
{
dbcsAttr.SetLeading();
}
else if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_TRAILING_BYTE))
{
dbcsAttr.SetTrailing();
}
const TextAttribute textAttr(charInfo.Attributes);
const auto behavior = TextAttributeBehavior::Stored;
return OutputCellView(glyph, dbcsAttr, textAttr, behavior);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// Arguments:
// - cell - A reference to the cell for which we will make the read-only view
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const OutputCell& cell)
{
return OutputCellView(cell.Chars(), cell.DbcsAttr(), cell.TextAttr(), cell.TextAttrBehavior());
}
// Routine Description:
// - Gets the distance between two iterators relative to the input data given in.
// Return Value:
// - The number of items of the input run consumed between these two iterators.
ptrdiff_t OutputCellIterator::GetInputDistance(OutputCellIterator other) const noexcept
{
return _pos - other._pos;
}
// Routine Description:
// - Gets the distance between two iterators relative to the number of cells inserted.
// Return Value:
// - The number of cells in the backing buffer filled between these two iterators.
ptrdiff_t OutputCellIterator::GetCellDistance(OutputCellIterator other) const noexcept
{
return _distance - other._distance;
}

View File

@@ -1,125 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- OutputCellIterator.hpp
Abstract:
- Read-only view into an entire batch of data to be written into the output buffer.
- This is done for performance reasons (avoid heap allocs and copies).
Author:
- Michael Niksa (MiNiksa) 06-Oct-2018
Revision History:
- Based on work from OutputCell.hpp/cpp by Austin Diviness (AustDi)
--*/
#pragma once
#include "TextAttribute.hpp"
#include "OutputCell.hpp"
#include "OutputCellView.hpp"
class OutputCellIterator final
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = OutputCellView;
using difference_type = ptrdiff_t;
using pointer = OutputCellView*;
using reference = OutputCellView&;
OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const std::wstring_view utf16Text);
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute);
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
OutputCellIterator(const gsl::span<const OutputCell> cells);
~OutputCellIterator() = default;
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
operator bool() const noexcept;
ptrdiff_t GetCellDistance(OutputCellIterator other) const noexcept;
ptrdiff_t GetInputDistance(OutputCellIterator other) const noexcept;
friend ptrdiff_t operator-(OutputCellIterator one, OutputCellIterator two) = delete;
OutputCellIterator& operator++();
OutputCellIterator operator++(int);
const OutputCellView& operator*() const noexcept;
const OutputCellView* operator->() const noexcept;
private:
enum class Mode
{
// Loose mode is where we're given text and attributes in a raw sort of form
// like while data is being inserted from an API call.
Loose,
// Loose mode with only text is where we're given just text and we want
// to use the attribute already in the buffer when writing
LooseTextOnly,
// Fill mode is where we were given one thing and we just need to keep giving
// that back over and over for eternity.
Fill,
// Given a run of legacy attributes, convert each of them and insert only attribute data.
LegacyAttr,
// CharInfo mode is where we've been given a pair of text and attribute for each
// cell in the legacy format from an API call.
CharInfo,
// Cell mode is where we have an already fully structured cell data usually
// from accessing/copying data already put into the OutputBuffer.
Cell,
};
Mode _mode;
gsl::span<const WORD> _legacyAttrs;
std::variant<
std::wstring_view,
gsl::span<const WORD>,
gsl::span<const CHAR_INFO>,
gsl::span<const OutputCell>,
std::monostate>
_run;
TextAttribute _attr;
bool _TryMoveTrailing() noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior);
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;
static OutputCellView s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept;
static OutputCellView s_GenerateView(const CHAR_INFO& charInfo) noexcept;
static OutputCellView s_GenerateView(const OutputCell& cell);
OutputCellView _currentView;
size_t _pos;
size_t _distance;
size_t _fillLimit;
};

View File

@@ -42,19 +42,6 @@ gsl::span<OutputCell> OutputCellRect::GetRow(const size_t row)
return gsl::span<OutputCell>(_FindRowOffset(row), _cols);
}
// Routine Description:
// - Gets a read-only iterator view over a single row of the rectangle.
// Arguments:
// - row - The Y position or row index in the buffer.
// Return Value:
// - Read-only iterator of OutputCells
OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const
{
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
return OutputCellIterator(view);
}
// Routine Description:
// - Internal helper to find the pointer to the specific row offset in the giant
// contiguous block of memory allocated for this rectangle.

View File

@@ -21,10 +21,8 @@ Revision History:
#pragma once
#include "DbcsAttribute.hpp"
#include "TextAttribute.hpp"
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
class OutputCellRect final
{
@@ -33,7 +31,6 @@ public:
OutputCellRect(const size_t rows, const size_t cols);
gsl::span<OutputCell> GetRow(const size_t row);
OutputCellIterator GetRowIter(const size_t row) const;
size_t Height() const noexcept;
size_t Width() const noexcept;

View File

@@ -6,7 +6,7 @@ Module Name:
- OutputCellView.hpp
Abstract:
- Read-only view into a single cell of data that someone is attempting to write into the output buffer.
- Read view into a single cell of data that someone is attempting to write into the output buffer.
- This is done for performance reasons (avoid heap allocs and copies).
Author:
@@ -36,6 +36,21 @@ public:
TextAttribute TextAttr() const noexcept;
TextAttributeBehavior TextAttrBehavior() const noexcept;
void UpdateText(const std::wstring_view& view) noexcept
{
_view = view;
};
void UpdateDbcsAttribute(const DbcsAttribute& dbcsAttr) noexcept
{
_dbcsAttr = dbcsAttr;
}
void UpdateTextAttribute(const TextAttribute& textAttr) noexcept
{
_textAttr = textAttr;
}
bool operator==(const OutputCellView& view) const noexcept;
bool operator!=(const OutputCellView& view) const noexcept;

View File

@@ -3,7 +3,6 @@
#include "precomp.h"
#include "Row.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/convert.hpp"
@@ -16,15 +15,16 @@
// - pParent - the text buffer that this row belongs to
// Return Value:
// - constructed object
ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) :
_id{ rowId },
_rowWidth{ rowWidth },
_charRow{ rowWidth, this },
_attrRow{ rowWidth, fillAttribute },
ROW::ROW(const SHORT /*rowId*/, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const /*pParent*/) :
_data{
std::wstring(rowWidth, UNICODE_SPACE),
{ { gsl::narrow_cast<uint8_t>(1), gsl::narrow_cast<uint16_t>(rowWidth) } },
{ rowWidth, fillAttribute },
rowWidth
},
_lineRendition{ LineRendition::SingleWidth },
_wrapForced{ false },
_doubleBytePadded{ false },
_pParent{ pParent }
_doubleBytePadded{ false }
{
}
@@ -39,10 +39,11 @@ bool ROW::Reset(const TextAttribute Attr)
_lineRendition = LineRendition::SingleWidth;
_wrapForced = false;
_doubleBytePadded = false;
_charRow.Reset();
_data._cwid.replace(0, _data._width, { 1, _data._width }); // replace entire RLE with one run
_data._data.replace(0, _data._width, _data._width, UNICODE_SPACE);
try
{
_attrRow.Reset(Attr);
_data._attrRow.Reset(Attr);
}
catch (...)
{
@@ -60,14 +61,20 @@ bool ROW::Reset(const TextAttribute Attr)
// - S_OK if successful, otherwise relevant error
[[nodiscard]] HRESULT ROW::Resize(const unsigned short width)
{
RETURN_IF_FAILED(_charRow.Resize(width));
_data._data.resize(width, L' ');
auto oldEnd{ _data._cwid.size() };
_data._cwid.resize_trailing_extent(width);
if (width > oldEnd)
{
_data._cwid.replace(oldEnd, width, { 1, gsl::narrow_cast<uint16_t>(width - oldEnd) });
}
try
{
_attrRow.Resize(width);
_data._attrRow.Resize(width);
}
CATCH_RETURN();
_rowWidth = width;
_data._width = width;
return S_OK;
}
@@ -80,20 +87,12 @@ bool ROW::Reset(const TextAttribute Attr)
// - <none>
void ROW::ClearColumn(const size_t column)
{
THROW_HR_IF(E_INVALIDARG, column >= _charRow.size());
_charRow.ClearCell(column);
}
UnicodeStorage& ROW::GetUnicodeStorage() noexcept
{
return _pParent->GetUnicodeStorage();
}
const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
{
return _pParent->GetUnicodeStorage();
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
WriteGlyphAtMeasured(column, 1, L" ");
}
#if 0
TODO (DH)
// Routine Description:
// - writes cell data to the row
// Arguments:
@@ -105,11 +104,11 @@ const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
// - iterator to first cell that was not written to this row.
OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap, std::optional<size_t> limitRight)
{
THROW_HR_IF(E_INVALIDARG, index >= _charRow.size());
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _charRow.size());
THROW_HR_IF(E_INVALIDARG, index >= _data._width);
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _data._width);
// If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row.
const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1);
const auto finalColumnInRow = limitRight.value_or(_data._width - 1);
auto currentColor = it->TextAttr();
uint16_t colorUses = 0;
@@ -131,7 +130,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
{
// Otherwise, commit this color into the run and save off the new one.
// Now commit the new color runs into the attr row.
_attrRow.Replace(colorStarts, currentIndex, currentColor);
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
currentColor = it->TextAttr();
colorUses = 1;
colorStarts = currentIndex;
@@ -151,21 +150,31 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
// Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
{
_charRow.ClearCell(currentIndex);
ClearColumn(currentIndex);
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
}
// If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
// Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
{
_charRow.ClearCell(currentIndex);
ClearColumn(currentIndex);
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
SetDoubleBytePadded(true);
}
// Otherwise, copy the data given and increment the iterator.
else
{
_charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
_charRow.GlyphAt(currentIndex) = it->Chars();
++it;
if (!it->DbcsAttr().IsTrailing())
{
uint16_t d = it->DbcsAttr().IsSingle() ? 1 : 2;
WriteGlyphAtMeasured(currentIndex, d, it->Chars());
currentIndex += d - 1;
while (d > 0)
{ // TODO(DH) FFS
++it;
--d;
}
}
}
// If we're asked to (un)set the wrap status and we just filled the last column with some text...
@@ -191,8 +200,9 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
// Now commit the final color into the attr row
if (colorUses)
{
_attrRow.Replace(colorStarts, currentIndex, currentColor);
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
}
return it;
}
#endif

View File

@@ -23,18 +23,157 @@ Revision History:
#include "AttrRow.hpp"
#include "LineRendition.hpp"
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
#include "CharRow.hpp"
#include "UnicodeStorage.hpp"
#include "unicode.hpp"
#pragma warning(push)
#pragma warning(disable : 4267)
class TextBuffer;
using RowMeasurementBuffer = til::small_rle<uint8_t, uint16_t, 3>;
struct DamageRanges
{
size_t dataOffset;
size_t dataLength;
uint16_t firstColumn;
uint16_t lastColumnExclusive;
};
template<typename TRuns>
static DamageRanges DamageRangesForColumnInMeasurementBuffer(const TRuns& cwid, size_t col)
{
size_t currentCol{ 0 };
size_t currentWchar{ 0 };
auto it{ cwid.runs().cbegin() };
while (it != cwid.runs().cend())
{
// Each compressed pair tells us how many columns x N wchar_t
const auto colsCoveredByRun{ it->value * it->length };
if (currentCol + colsCoveredByRun > col)
{
// We want to break out of the loop to manually handle this run, because
// we've determined that it is the run that covers the column of interest.
break;
}
currentCol += colsCoveredByRun;
currentWchar += it->length;
it++;
}
if (it == cwid.runs().cend())
{
// this is an interesting case- somebody requested a column we cannot answer for.
// The string might actually have data, and the caller might be interested in where that data is.
// Ideally, we would return the index of the first char out-of-bounds, and the length of the remaining data as a single unit.
// We can't answer for how much space it takes up, though.
__debugbreak();
return { 0, 0, 0u, 0u };
//return { currentWchar, _data.size() - currentWchar, 0u, 0u };
}
// currentWchar is how many wchar_t we are into the string before processing this run
// currentCol is how many columns we've covered before processing this run
// We are *guaranteed* that the hit is in this run -- no need to check it->length
// col-currentCol is how many columns are left unaccounted for (how far into this run we need to go)
const auto colsLeftToCountInCurrentRun{ col - currentCol };
currentWchar += colsLeftToCountInCurrentRun / it->value; // one wch per column unit -- rounds down (correct behavior)
size_t lenInWchars{ 1 }; // the first hit takes up one wchar
// We use this to determine if we have exhausted every column this run can cough up.
// colsLeftToCountInCurrentRun is 0-indexed, but colsConsumedByRun is 1-indexed (index 0 consumes N columns, etc.)
// therefore, we reindex colsLeftToCountInCurrentRun and compare it to colsConsumedByRun
const auto colsConsumedFromRun{ colsLeftToCountInCurrentRun + it->value };
const auto colsCoveredByRun{ it->value * it->length };
// If we *have* consumed every column this run can provide, we must check the run after it:
// if it contributes "0" columns, it is actually a set of trailing code units.
if (colsConsumedFromRun >= colsCoveredByRun && it != cwid.runs().cend())
{
const auto nextRunIt{ it + 1 };
if (nextRunIt != cwid.runs().cend() && nextRunIt->value == 0)
{
// we were at the boundary of a column run, so if the next one is 0 it tells us that each
// wchar after it is a trailer
lenInWchars += nextRunIt->length;
}
}
return {
currentWchar, // wchar start
lenInWchars, // wchar size
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value)), // Column damage to the left (where we overlapped the right of a wide glyph)
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value) + it->value), // Column damage to the right (where we overlapped the left of a wide glyph)
};
}
class ROW;
struct RowImage
{
std::wstring _data;
RowMeasurementBuffer _cwid;
ATTR_ROW _attrRow;
uint16_t _width;
friend class ROW;
friend class TextBuffer;
RowImage() :
_data{}, _cwid{}, _attrRow{ {} }, _width{ 0 } {}
RowImage(const std::wstring& data, const RowMeasurementBuffer& cwid, ATTR_ROW attrRow, uint16_t width):
_data{data}, _cwid{cwid}, _attrRow{std::move(attrRow)}, _width{width} {}
// exclusive
std::tuple<RowImage, RowImage> split(uint16_t col) const
{
if (col >= _width)
{
return { *this, RowImage{} };
}
else if (col == 0)
{
return { RowImage{}, *this };
}
auto yes_more_fucking_damage_ranges = DamageRangesForColumnInMeasurementBuffer(_cwid, col);
// here's a dumb decision: when you split along a wide char, you get spaces over its damage on the left side.
// X X XX X X X
// ^ split
// X X S <- left
// XX X X X <- right
auto chopped_off = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? 0 : /*didn't get whole thing*/ yes_more_fucking_damage_ranges.lastColumnExclusive - col;
auto chopped_on = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? yes_more_fucking_damage_ranges.dataLength : 0;
auto lwid = _cwid.slice(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on);
for (auto z{ chopped_off }; z > 0; --z)
{
lwid.append(1);
}
RowImage left{
_data.substr(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on) + std::wstring(chopped_off, UNICODE_SPACE),
lwid,
_attrRow._data.slice(0, yes_more_fucking_damage_ranges.lastColumnExclusive),
col
};
RowImage right{
_data.substr(yes_more_fucking_damage_ranges.dataOffset + chopped_on),
_cwid.slice(yes_more_fucking_damage_ranges.dataOffset + chopped_on, _data.length()),
// we use first col here to catch the overlap
_attrRow._data.slice(yes_more_fucking_damage_ranges.firstColumn, _width),
::base::ClampSub(_width, col),
};
return { left, right };
}
};
enum class DelimiterClass
{
ControlChar,
DelimiterChar,
RegularChar
};
class ROW final
{
public:
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
size_t size() const noexcept { return _rowWidth; }
size_t size() const noexcept { return _data._width; }
void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; }
bool WasWrapForced() const noexcept { return _wrapForced; }
@@ -42,52 +181,357 @@ public:
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; }
bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; }
const CharRow& GetCharRow() const noexcept { return _charRow; }
CharRow& GetCharRow() noexcept { return _charRow; }
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
const ATTR_ROW& GetAttrRow() const noexcept { return _data._attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _data._attrRow; }
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
SHORT GetId() const noexcept { return _id; }
void SetId(const SHORT id) noexcept { _id = id; }
bool Reset(const TextAttribute Attr);
[[nodiscard]] HRESULT Resize(const unsigned short width);
void ClearColumn(const size_t column);
std::wstring GetText() const { return _charRow.GetText(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap = std::nullopt, std::optional<size_t> limitRight = std::nullopt);
std::wstring GetText() const { return _data._data; }
#ifdef UNIT_TESTING
friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept;
friend class RowTests;
#endif
struct RowData
{
std::wstring _data;
RowMeasurementBuffer _cwid;
ATTR_ROW _attrRow;
unsigned short _width;
DamageRanges _damageForColumn(size_t col) const
{
return DamageRangesForColumnInMeasurementBuffer(_cwid, col);
}
DamageRanges _damageForColumns(size_t col, size_t ncols) const
{
// When we want to replace a column, or set of columns, with a glyph, we need to:
// * Figure out the physical extent of the character in that cell (UTF-16 code units).
// * Figure out the columnar extent of the character in that cell (how many columns it covers).
// * In the simple case (1->1, 2->2), there will be no damage.
// * In the complex case (2->1, 1->2, 2->2 with middle overlap), there *WILL* be damage.
// * Replace the physical character data in that cell with the new character data.
// * Insert padding characters to the left and right to account for damage.
//
// ## DAMAGE
// Damage is measured in the number of columns to the left
// and right of the new glyph that are now NO LONGER VALID because
// they were double-width characters that are being cut in half,
// or single-width characters that are collateral damage from stomping
// them with a double-width character.
auto damage{ _damageForColumn(col) };
const auto lastDamage{ _damageForColumn(col + ncols - 1 /*inclusive*/) };
// *INVARIANT* the beginning of the next column range must have a different beginning byte
// This column began at a different data index, so we have to delete its data too.
// Since it's contiguous, just increment len.
damage.dataLength = lastDamage.dataOffset + lastDamage.dataLength - damage.dataOffset;
damage.lastColumnExclusive = lastDamage.lastColumnExclusive;
return damage;
}
void _strikeDamageAndAdjust(size_t col, size_t ncols, size_t incomingCodeUnitCount, DamageRanges& range)
{
(void)incomingCodeUnitCount;
const bool damaged{ range.firstColumn < col || col + ncols < range.lastColumnExclusive };
if (damaged)
{
const auto damagedColumns{ range.lastColumnExclusive - range.firstColumn };
_data.replace(range.dataOffset, range.dataLength, damagedColumns, UNICODE_SPACE);
_cwid.replace(range.dataOffset, range.dataOffset + range.dataLength, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(damagedColumns) });
// We may have replaced surrogate pairs/etc with fewer/more code units.
range.dataLength = damagedColumns;
}
}
};
private:
CharRow _charRow;
ATTR_ROW _attrRow;
RowData _data;
LineRendition _lineRendition;
SHORT _id;
unsigned short _rowWidth;
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
bool _wrapForced;
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
bool _doubleBytePadded;
TextBuffer* _pParent; // non ownership pointer
public:
std::wstring_view GlyphAt(size_t col) const
{
const auto lookup{ _data._damageForColumn(col) };
return { _data._data.data() + lookup.dataOffset, lookup.dataLength };
}
std::pair<size_t, size_t> WriteGlyphAtMeasured(size_t col, size_t ncols, std::wstring_view glyph)
{
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, ncols) };
if (minDamageColumn == col && maxDamageColumnExclusive == col + ncols)
{
// We are only damaging as many columns as we are introducing -- no spillover (!)
// We can replace the code units in the data directly, and we can replace the
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
// for any code units past the first.)
_data._data.replace(begin, len, glyph);
typename decltype(_data._cwid)::rle_type newRuns[]{
{ gsl::narrow_cast<uint8_t>(ncols), 1 },
{ 0, gsl::narrow_cast<uint16_t>(glyph.size() - 1) },
};
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(&newRuns[0], glyph.size() == 1 ? 1 : 2));
}
else
{
// We are damaging multiple columns -- oops. We need to insert replacement characters
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
// our newly-inserted region. We also need to insert replacement characters from the
// rightmost side of our glyph to the rightmost side of the glyph that was once in
// that column.
// Left side count : col - minDamageColumn
// Right side count: maxDamageColumn - (col + ncols)
const auto replacementCodeUnits{ (col - minDamageColumn) + glyph.size() + (maxDamageColumnExclusive - (col + ncols)) };
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
replacement.replace(col - minDamageColumn, glyph.size(), glyph);
// New advances:
// Our glyph and all its trailers
// v-----v
// [1, ..., 1, X, 0, 0, 1, ..., 1]
// ^-------^ ^-------^
// Each replacement space char
// is one column wide. We have
// to insert [1]s for each
// damaged column.
boost::container::small_vector<typename decltype(_data._cwid)::rle_type, 4> newRuns;
if (col - minDamageColumn)
{
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(col - minDamageColumn));
}
newRuns.emplace_back(gsl::narrow_cast<uint8_t>(ncols), (uint16_t)1);
if (glyph.size() > 1)
{
newRuns.emplace_back((uint8_t)0, gsl::narrow_cast<uint16_t>(glyph.size() - 1)); // trailers
}
if (maxDamageColumnExclusive - (col + ncols))
{
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + ncols)));
}
_data._data.replace(begin, len, replacement);
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(newRuns));
}
// Distance from requested column to final
_maxc = std::max(_maxc, maxDamageColumnExclusive);
return { begin + glyph.size(), col + ncols };
}
DbcsAttribute DbcsAttrAt(size_t col) const
{
const auto [begin, len, first, lastE] = _data._damageForColumn(col);
if (lastE - first == 1)
{
// The glyph under this column is only onw column wide.
return DbcsAttribute{ DbcsAttribute::Attribute::Single };
}
else if (first != col)
{
// The glyph under this column is >1 col wide, and we're bisecting it
return DbcsAttribute{ DbcsAttribute::Attribute::Trailing };
}
else
{
// The glyph under this column is >1 col wide, and we're at the head
return DbcsAttribute{ DbcsAttribute::Attribute::Leading };
}
}
std::tuple<size_t, uint16_t, uint16_t> WriteStringAtMeasured(uint16_t col, uint16_t colCount, const std::wstring_view& string, const RowMeasurementBuffer& measurements)
{
size_t incomingLastColumn{ std::min<size_t>(_data._width - col, colCount) };
auto incomingLastColumnOffsets{ DamageRangesForColumnInMeasurementBuffer(measurements, incomingLastColumn - 1 /*inclusive*/) };
auto codeUnitsToConsume{ incomingLastColumnOffsets.dataOffset };
auto columnsToConsume{ incomingLastColumn };
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, incomingLastColumn) };
// If these don't match, we are cutting a multi-cell glyph.
if (incomingLastColumnOffsets.lastColumnExclusive == incomingLastColumn)
{
// Since they *do* match, we should consume this part of the string too.
codeUnitsToConsume += incomingLastColumnOffsets.dataLength;
}
else
{
// Only consume up to the final cell (the one we cut in half)
columnsToConsume = incomingLastColumnOffsets.firstColumn;
// **INVARIANT** we only get here if we had to cut off the incoming text, and that only
// happens because we had to clamp the read buffer against our width. This means that the
// incoming text definitely had a wide glyph that would not fit against the end of our
// buffer.
// THEREFORE: col+incomingLastColumn was our final column (exclusive)
// which means that maxDamageColumnExclusive can be upgraded to be our width.
// OPTIMIZATION: If we mark our last column as damaged, it will automatically get stomped
// with spaces.
// NO NO NO NO NO NO NO NO NO NO TODO(DH)
// We can't do this without changing the damaged buffer region to delete the character
// from _data at the same time. Use the non-optimial path.
ClearColumn(_data._width - 1);
SetDoubleBytePadded(true);
// NO - we already marked this column when we calculated damage above
//++columnsWrittenAfterInsertionPoint;
}
auto mss = measurements.slice(0, gsl::narrow_cast<uint16_t>(codeUnitsToConsume));
if (minDamageColumn == col && maxDamageColumnExclusive == col + incomingLastColumn)
{
// We are only damaging as many columns as we are introducing -- no spillover (!)
// We can replace the code units in the data directly, and we can replace the
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
// for any code units past the first.)
_data._data.replace(begin, len, &*string.cbegin(), codeUnitsToConsume);
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs());
}
else
{
// We are damaging multiple columns -- oops. We need to insert replacement characters
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
// our newly-inserted region. We also need to insert replacement characters from the
// rightmost side of our glyph to the rightmost side of the glyph that was once in
// that column.
// Left side count : col - minDamageColumn
// Right side count: maxDamageColumn - (col + ncols)
const auto replacementCodeUnits{ (col - minDamageColumn) + codeUnitsToConsume + (maxDamageColumnExclusive - (col + incomingLastColumn)) };
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
replacement.replace(col - minDamageColumn, codeUnitsToConsume, &*string.cbegin(), codeUnitsToConsume);
// New advances:
// Our glyph and all its trailers
// v-----v
// [1, ..., 1, X, 0, 0, 1, ..., 1]
// ^-------^ ^-------^
// Each replacement space char
// is one column wide. We have
// to insert [1]s for each
// damaged column.
mss.replace(0, 0, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(col - minDamageColumn) });
mss.replace(mss.size(), mss.size(), { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + incomingLastColumn)) });
_data._data.replace(begin, len, replacement);
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs()); //gsl::make_span(newRuns));
}
return {
codeUnitsToConsume,
gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - col),
gsl::narrow_cast<uint16_t>(columnsToConsume)
};
}
size_t Fill(size_t col, size_t count, wchar_t ch, uint8_t w)
{
const auto charsFitOrRemain = std::min((_data._width - col) / w, count);
const auto columnsRequired{ charsFitOrRemain * w };
auto damage = _data._damageForColumns(col, columnsRequired);
// If we are filling over the left/right halves of a character
// This is a bit wasteful since it can grow/shrink the buffers and we're about
// to do it again, but I was trying to be expedient.
_data._strikeDamageAndAdjust(col, columnsRequired, charsFitOrRemain, damage);
const auto [begin, len, min, max] = damage;
_data._data.replace(begin, len, charsFitOrRemain, ch);
_data._cwid.replace(begin, begin + len, { w, gsl::narrow_cast<uint16_t>(charsFitOrRemain) });
const auto doubleBytePadded{
w > 1 // We had a wide glyph...
&& max != _data._width // ...and didn't reach the edge
&& count > charsFitOrRemain // ...but we had spare characters, so we wanted to
};
if (doubleBytePadded)
{
const uint16_t remaining{ gsl::narrow_cast<uint16_t>(_data._width - max) };
// overflow: add spaces
_data._data.replace(begin + charsFitOrRemain, _data._data.size() - begin + charsFitOrRemain, remaining, UNICODE_SPACE);
_data._cwid.replace(begin + charsFitOrRemain, _data._cwid.size(), { uint8_t{ 1u }, gsl::narrow_cast<uint16_t>(remaining) });
}
if (max == _data._width || doubleBytePadded)
{
// TODO(DH): Evaluate the above condition
// We only want to do this if we touched or near-touched the lat col
SetDoubleBytePadded(doubleBytePadded);
SetWrapForced(false);
}
return charsFitOrRemain;
}
// Method Description:
// - get delimiter class for a position in the char row
// - used for double click selection and uia word navigation
// Arguments:
// - column: column to get text data for
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
// Return Value:
// - the delimiter class for the given char
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
{
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
const auto glyph = *GlyphAt(column).begin();
if (glyph <= UNICODE_SPACE)
{
return DelimiterClass::ControlChar;
}
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
}
RowImage Dump(uint16_t left, uint16_t size)
{
auto [begin, len, min, max] = _data._damageForColumns(left, size);
return RowImage{
_data._data.substr(begin, len),
_data._cwid.slice(begin, begin + len),
ATTR_ROW{ _data._attrRow._data.slice(min, max) },
::base::MakeClampedNum(max) - min
};
}
void Reinsert(uint16_t left, const RowImage& ri)
{
auto damage{ _data._damageForColumns(left, ri._width) };
_data._strikeDamageAndAdjust(left, ri._width, ri._data.size(), damage);
_data._data.replace(damage.dataOffset, damage.dataLength, ri._data);
_data._cwid.replace(damage.dataOffset, damage.dataOffset + damage.dataLength, ri._cwid.runs());
_data._attrRow._data.replace(damage.firstColumn, damage.lastColumnExclusive, ri._attrRow._data.runs());
}
uint16_t _maxc{};
size_t MeasureRight() const
{
return _maxc;
}
};
#ifdef UNIT_TESTING
constexpr bool operator==(const ROW& a, const ROW& b) noexcept
{
// comparison is only used in the tests; this should suffice.
return (a._pParent == b._pParent &&
a._id == b._id);
return (a._data == b._data &&
a._cwid == b._cwid &&
a._attrRow == b._attrRow &&
a._rowWidth == b._rowWidth &&
a._wrapForced == b._wrapForced &&
a._doubleBytePadded == b._doubleBytePadded);
}
#endif
#pragma warning(pop)

View File

@@ -1,98 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "UnicodeStorage.hpp"
UnicodeStorage::UnicodeStorage() noexcept :
_map{}
{
}
// Routine Description:
// - fetches the text associated with key
// Arguments:
// - key - the key into the storage
// Return Value:
// - the glyph data associated with key
// Note: will throw exception if key is not stored yet
const UnicodeStorage::mapped_type& UnicodeStorage::GetText(const key_type key) const
{
return _map.at(key);
}
// Routine Description:
// - stores glyph data associated with key.
// Arguments:
// - key - the key into the storage
// - glyph - the glyph data to store
void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
{
_map.insert_or_assign(key, glyph);
}
// Routine Description:
// - erases key and its associated data from the storage
// Arguments:
// - key - the key to remove
void UnicodeStorage::Erase(const key_type key) noexcept
{
_map.erase(key);
}
// Routine Description:
// - Remaps all of the stored items to new coordinate positions
// based on a bulk rearrangement of row IDs and potential row width resize.
// Arguments:
// - rowMap - A map of the old row IDs to the new row IDs.
// - width - The width of the new row. Remove any items that are beyond the row width.
// - Use nullopt if we're not resizing the width of the row, just renumbering the rows.
void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
{
// Make a temporary map to hold all the new row positioning
std::unordered_map<key_type, mapped_type> newMap;
// Walk through every stored item.
for (const auto& pair : _map)
{
// Extract the old coordinate position
const auto oldCoord = pair.first;
// Only try to short-circuit based on width if we were told it changed
// by being given a new width value.
if (width.has_value())
{
// Get the column ID
const auto oldColId = oldCoord.X;
// If the column index is at/beyond the row width, don't bother copying it to the new map.
if (oldColId >= width.value())
{
continue;
}
}
// Get the row ID from the position as that's what we need to remap
const auto oldRowId = oldCoord.Y;
// Use the mapping given to convert the old row ID to the new row ID
const auto mapIter = rowMap.find(oldRowId);
// If there's no mapping to a new row, don't bother copying it to the new map. The row is gone.
if (mapIter == rowMap.end())
{
continue;
}
const auto newRowId = mapIter->second;
// Generate a new coordinate with the same X as the old one, but a new Y value.
const auto newCoord = COORD{ oldCoord.X, newRowId };
// Put the adjusted coordinate into the map with the original value.
newMap.emplace(newCoord, pair.second);
}
// Swap into the stored map, free the temporary when we exit.
_map.swap(newMap);
}

View File

@@ -1,67 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- UnicodeStorage.hpp
Abstract:
- dynamic storage location for glyphs that can't normally fit in the output buffer
Author(s):
- Austin Diviness (AustDi) 02-May-2018
--*/
#pragma once
#include <vector>
#include <unordered_map>
#include <climits>
// std::unordered_map needs help to know how to hash a COORD
namespace std
{
template<>
struct hash<COORD>
{
// Routine Description:
// - hashes a coord. coord will be hashed by storing the x and y values consecutively in the lower
// bits of a size_t.
// Arguments:
// - coord - the coord to hash
// Return Value:
// - the hashed coord
constexpr size_t operator()(const COORD& coord) const noexcept
{
size_t retVal = coord.Y;
const size_t xCoord = coord.X;
retVal |= xCoord << (sizeof(coord.Y) * CHAR_BIT);
return retVal;
}
};
}
class UnicodeStorage final
{
public:
using key_type = typename COORD;
using mapped_type = typename std::vector<wchar_t>;
UnicodeStorage() noexcept;
const mapped_type& GetText(const key_type key) const;
void StoreGlyph(const key_type key, const mapped_type& glyph);
void Erase(const key_type key) noexcept;
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
private:
std::unordered_map<key_type, mapped_type> _map;
#ifdef UNIT_TESTING
friend class UnicodeStorageTests;
friend class TextBufferTests;
#endif
};

View File

@@ -315,6 +315,11 @@ void Cursor::StartDeferDrawing() noexcept
_fDeferCursorRedraw = true;
}
bool Cursor::IsDeferDrawing() noexcept
{
return _fDeferCursorRedraw;
}
void Cursor::EndDeferDrawing() noexcept
{
if (_fHaveDeferredCursorRedraw)

View File

@@ -55,6 +55,7 @@ public:
const COLORREF GetColor() const noexcept;
void StartDeferDrawing() noexcept;
bool IsDeferDrawing() noexcept;
void EndDeferDrawing() noexcept;
void SetHasMoved(const bool fHasMoved) noexcept;

View File

@@ -13,7 +13,6 @@
<ClCompile Include="..\AttrRow.cpp" />
<ClCompile Include="..\cursor.cpp" />
<ClCompile Include="..\OutputCell.cpp" />
<ClCompile Include="..\OutputCellIterator.cpp" />
<ClCompile Include="..\OutputCellRect.cpp" />
<ClCompile Include="..\OutputCellView.cpp" />
<ClCompile Include="..\Row.cpp" />
@@ -23,22 +22,16 @@
<ClCompile Include="..\textBuffer.cpp" />
<ClCompile Include="..\textBufferCellIterator.cpp" />
<ClCompile Include="..\textBufferTextIterator.cpp" />
<ClCompile Include="..\CharRow.cpp" />
<ClCompile Include="..\CharRowCell.cpp" />
<ClCompile Include="..\CharRowCellReference.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\UnicodeStorage.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\AttrRow.hpp" />
<ClInclude Include="..\cursor.h" />
<ClInclude Include="..\DbcsAttribute.hpp" />
<ClInclude Include="..\ICharRow.hpp" />
<ClInclude Include="..\LineRendition.hpp" />
<ClInclude Include="..\OutputCell.hpp" />
<ClInclude Include="..\OutputCellIterator.hpp" />
<ClInclude Include="..\OutputCellRect.hpp" />
<ClInclude Include="..\OutputCellView.hpp" />
<ClInclude Include="..\Row.hpp" />
@@ -48,11 +41,7 @@
<ClInclude Include="..\textBuffer.hpp" />
<ClInclude Include="..\textBufferCellIterator.hpp" />
<ClInclude Include="..\textBufferTextIterator.hpp" />
<ClInclude Include="..\CharRow.hpp" />
<ClInclude Include="..\CharRowCell.hpp" />
<ClInclude Include="..\CharRowCellReference.hpp" />
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\UnicodeStorage.hpp" />
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />

View File

@@ -5,7 +5,6 @@
#include "search.h"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/Utf16Parser.hpp"
#include "../types/inc/GlyphWidth.hpp"

View File

@@ -41,10 +41,6 @@ SOURCES= \
..\textBuffer.cpp \
..\textBufferCellIterator.cpp \
..\textBufferTextIterator.cpp \
..\CharRow.cpp \
..\CharRowCell.cpp \
..\CharRowCellReference.cpp \
..\UnicodeStorage.cpp \
..\search.cpp \
INCLUDES= \

View File

@@ -4,11 +4,11 @@
#include "precomp.h"
#include "textBuffer.hpp"
#include "CharRow.hpp"
#include "../types/inc/utils.hpp"
#include "../types/inc/convert.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include "../types/inc/Utf16Parser.hpp"
#pragma hdrstop
@@ -35,7 +35,6 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
_currentAttributes{ defaultAttributes },
_cursor{ cursorSize, *this },
_storage{},
_unicodeStorage{},
_renderTarget{ renderTarget },
_size{},
_currentHyperlinkId{ 1 },
@@ -184,278 +183,6 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport
return TextBufferCellIterator(*this, at, limit);
}
//Routine Description:
// - Corrects and enforces consistent double byte character state (KAttrs line) within a row of the text buffer.
// - This will take the given double byte information and check that it will be consistent when inserted into the buffer
// at the current cursor position.
// - It will correct the buffer (by erasing the character prior to the cursor) if necessary to make a consistent state.
//Arguments:
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
//Return Value:
// - True if it is valid to insert a character with the given double byte attributes. False otherwise.
bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute)
{
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
const COORD coordPrevPosition = _GetPreviousFromCursor();
ROW& prevRow = GetRowByOffset(coordPrevPosition.Y);
DbcsAttribute prevDbcsAttr;
try
{
prevDbcsAttr = prevRow.GetCharRow().DbcsAttrAt(coordPrevPosition.X);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return false;
}
bool fValidSequence = true; // Valid until proven otherwise
bool fCorrectableByErase = false; // Can't be corrected until proven otherwise
// Here's the matrix of valid items:
// N = None (single byte)
// L = Lead (leading byte of double byte sequence
// T = Trail (trailing byte of double byte sequence
// Prev Curr Result
// N N OK.
// N L OK.
// N T Fail, uncorrectable. Trailing byte must have had leading before it.
// L N Fail, OK with erase. Lead needs trailing pair. Can erase lead to correct.
// L L Fail, OK with erase. Lead needs trailing pair. Can erase prev lead to correct.
// L T OK.
// T N OK.
// T L OK.
// T T Fail, uncorrectable. New trailing byte must have had leading before it.
// Check for only failing portions of the matrix:
if (prevDbcsAttr.IsSingle() && dbcsAttribute.IsTrailing())
{
// N, T failing case (uncorrectable)
fValidSequence = false;
}
else if (prevDbcsAttr.IsLeading())
{
if (dbcsAttribute.IsSingle() || dbcsAttribute.IsLeading())
{
// L, N and L, L failing cases (correctable)
fValidSequence = false;
fCorrectableByErase = true;
}
}
else if (prevDbcsAttr.IsTrailing() && dbcsAttribute.IsTrailing())
{
// T, T failing case (uncorrectable)
fValidSequence = false;
}
// If it's correctable by erase, erase the previous character
if (fCorrectableByErase)
{
// Erase previous character into an N type.
try
{
prevRow.ClearColumn(coordPrevPosition.X);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return false;
}
// Sequence is now N N or N L, which are both okay. Set sequence back to valid.
fValidSequence = true;
}
return fValidSequence;
}
//Routine Description:
// - Call before inserting a character into the buffer.
// - This will ensure a consistent double byte state (KAttrs line) within the text buffer
// - It will attempt to correct the buffer if we're inserting an unexpected double byte character type
// and it will pad out the buffer if we're going to split a double byte sequence across two rows.
//Arguments:
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
//Return Value:
// - true if we successfully prepared the buffer and moved the cursor
// - false otherwise (out of memory)
bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute)
{
// This function corrects most errors. If this is false, we had an uncorrectable one which
// older versions of conhost simply let pass by unflinching.
LOG_HR_IF(E_NOT_VALID_STATE, !(_AssertValidDoubleByteSequence(dbcsAttribute))); // Shouldn't be uncorrectable sequences unless something is very wrong.
bool fSuccess = true;
// Now compensate if we don't have enough space for the upcoming double byte sequence
// We only need to compensate for leading bytes
if (dbcsAttribute.IsLeading())
{
const auto cursorPosition = GetCursor().GetPosition();
const auto lineWidth = GetLineWidth(cursorPosition.Y);
// If we're about to lead on the last column in the row, we need to add a padding space
if (cursorPosition.X == lineWidth - 1)
{
// set that we're wrapping for double byte reasons
auto& row = GetRowByOffset(cursorPosition.Y);
row.SetDoubleBytePadded(true);
// then move the cursor forward and onto the next row
fSuccess = IncrementCursor();
}
}
return fSuccess;
}
// Routine Description:
// - Writes cells to the output buffer. Writes at the cursor.
// Arguments:
// - givenIt - Iterator representing output cell data to write
// Return Value:
// - The final position of the iterator
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt)
{
const auto& cursor = GetCursor();
const auto target = cursor.GetPosition();
const auto finalIt = Write(givenIt, target);
return finalIt;
}
// Routine Description:
// - Writes cells to the output buffer.
// Arguments:
// - givenIt - Iterator representing output cell data to write
// - target - the row/column to start writing the text to
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data
// Return Value:
// - The final position of the iterator
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> wrap)
{
// Make mutable copy so we can walk.
auto it = givenIt;
// Make mutable target so we can walk down lines.
auto lineTarget = target;
// Get size of the text buffer so we can stay in bounds.
const auto size = GetSize();
// While there's still data in the iterator and we're still targeting in bounds...
while (it && size.IsInBounds(lineTarget))
{
// Attempt to write as much data as possible onto this line.
// NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line
it = WriteLine(it, lineTarget, wrap);
// Move to the next line down.
lineTarget.X = 0;
++lineTarget.Y;
}
return it;
}
// Routine Description:
// - Writes one line of text to the output buffer.
// Arguments:
// - givenIt - The iterator that will dereference into cell data to insert
// - target - Coordinate targeted within output buffer
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data in the iterator.
// - limitRight - Optionally restrict the right boundary for writing (e.g. stop writing earlier than the end of line)
// Return Value:
// - The iterator, but advanced to where we stopped writing. Use to find input consumed length or cells written length.
OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> wrap,
std::optional<size_t> limitRight)
{
// If we're not in bounds, exit early.
if (!GetSize().IsInBounds(target))
{
return givenIt;
}
// Get the row and write the cells
ROW& row = GetRowByOffset(target.Y);
const auto newIt = row.WriteCells(givenIt, target.X, wrap, limitRight);
// Take the cell distance written and notify that it needs to be repainted.
const auto written = newIt.GetCellDistance(givenIt);
const Viewport paint = Viewport::FromDimensions(target, { gsl::narrow<SHORT>(written), 1 });
_NotifyPaint(paint);
return newIt;
}
//Routine Description:
// - Inserts one codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
//Arguments:
// - chars - The codepoint to insert
// - dbcsAttribute - Double byte information associated with the codepoint
// - bAttr - Color data associated with the character
//Return Value:
// - true if we successfully inserted the character
// - false otherwise (out of memory)
bool TextBuffer::InsertCharacter(const std::wstring_view chars,
const DbcsAttribute dbcsAttribute,
const TextAttribute attr)
{
// Ensure consistent buffer state for double byte characters based on the character type we're about to insert
bool fSuccess = _PrepareForDoubleByteSequence(dbcsAttribute);
if (fSuccess)
{
// Get the current cursor position
short const iRow = GetCursor().GetPosition().Y; // row stored as logical position, not array position
short const iCol = GetCursor().GetPosition().X; // column logical and array positions are equal.
// Get the row associated with the given logical position
ROW& Row = GetRowByOffset(iRow);
// Store character and double byte data
CharRow& charRow = Row.GetCharRow();
short const cBufferWidth = GetSize().Width();
try
{
charRow.GlyphAt(iCol) = chars;
charRow.DbcsAttrAt(iCol) = dbcsAttribute;
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return false;
}
// Store color data
fSuccess = Row.GetAttrRow().SetAttrToEnd(iCol, attr);
if (fSuccess)
{
// Advance the cursor
fSuccess = IncrementCursor();
}
}
return fSuccess;
}
//Routine Description:
// - Inserts one ucs2 codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
//Arguments:
// - wch - The codepoint to insert
// - dbcsAttribute - Double byte information associated with the codepoint
// - bAttr - Color data associated with the character
//Return Value:
// - true if we successfully inserted the character
// - false otherwise (out of memory)
bool TextBuffer::InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr)
{
return InsertCharacter({ &wch, 1 }, dbcsAttribute, attr);
}
//Routine Description:
// - Finds the current row in the buffer (as indicated by the cursor position)
// and specifies that we have forced a line wrap on that row
@@ -607,7 +334,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
const auto& currRow = GetRowByOffset(coordEndOfText.Y);
// The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character)
coordEndOfText.X = gsl::narrow<short>(currRow.GetCharRow().MeasureRight()) - 1;
coordEndOfText.X = gsl::narrow<short>(currRow.MeasureRight()) - 1;
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
const auto viewportTop = viewport.Top();
@@ -618,7 +345,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
const auto& backupRow = GetRowByOffset(coordEndOfText.Y);
// We need to back up to the previous row if this line is empty, AND there are more rows
coordEndOfText.X = gsl::narrow<short>(backupRow.GetCharRow().MeasureRight()) - 1;
coordEndOfText.X = gsl::narrow<short>(backupRow.MeasureRight()) - 1;
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
}
@@ -778,7 +505,6 @@ void TextBuffer::ScrollRows(const SHORT firstRow, const SHORT size, const SHORT
}
// Renumber the IDs now that we've rearranged where the rows sit within the buffer.
// Refreshing should also delegate to the UnicodeStorage to re-key all the stored unicode sequences (where applicable).
_RefreshRowIDs(std::nullopt);
}
@@ -815,13 +541,11 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
// And if it's no longer single width, the right half of the row should be erased.
if (lineRendition != LineRendition::SingleWidth)
{
const auto fillChar = L' ';
auto fillAttrs = GetCurrentAttributes();
fillAttrs.SetStandardErase();
const size_t fillOffset = GetLineWidth(rowIndex);
const size_t fillLength = GetSize().Width() - fillOffset;
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
row.WriteCells(fillData, fillOffset, false);
FillWithCharacterAndAttributeLinear(til::point{ fillOffset, ::base::saturated_cast<size_t>(cursorPosition.Y) }, fillLength, UNICODE_SPACE, fillAttrs);
// We also need to make sure the cursor is clamped within the new width.
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
}
@@ -931,8 +655,7 @@ void TextBuffer::Reset()
}
// Now that we've tampered with the row placement, refresh all the row IDs.
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension
// and cleanup the UnicodeStorage characters that might fall outside the resized buffer.
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension.
_RefreshRowIDs(newSize.X);
// Update the cached size value
@@ -943,40 +666,16 @@ void TextBuffer::Reset()
return S_OK;
}
const UnicodeStorage& TextBuffer::GetUnicodeStorage() const noexcept
{
return _unicodeStorage;
}
UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
{
return _unicodeStorage;
}
// Routine Description:
// - Method to help refresh all the Row IDs after manipulating the row
// by shuffling pointers around.
// - This will also update parent pointers that are stored in depth within the buffer
// (e.g. it will update CharRow parents pointing at Rows that might have been moved around)
// - Optionally takes a new row width if we're resizing to perform a resize operation and cleanup
// any high unicode (UnicodeStorage) runs while we're already looping through the rows.
// - Optionally takes a new row width if we're resizing to perform a resize operation
// Arguments:
// - newRowWidth - Optional new value for the row width.
void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
{
std::unordered_map<SHORT, SHORT> rowMap;
SHORT i = 0;
for (auto& it : _storage)
{
// Build a map so we can update Unicode Storage
rowMap.emplace(it.GetId(), i);
// Update the IDs
it.SetId(i++);
// Also update the char row parent pointers as they can get shuffled up in the rotates.
it.GetCharRow().UpdateParent(&it);
// Resize the rows in the X dimension if we have a new width
if (newRowWidth.has_value())
{
@@ -984,9 +683,6 @@ void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
THROW_IF_FAILED(it.Resize(newRowWidth.value()));
}
}
// Give the new mapping to Unicode Storage
_unicodeStorage.Remap(rowMap, newRowWidth);
}
void TextBuffer::_NotifyPaint(const Viewport& viewport) const
@@ -1005,27 +701,6 @@ ROW& TextBuffer::_GetFirstRow()
return GetRowByOffset(0);
}
// Routine Description:
// - Retrieves the row that comes before the given row.
// - Does not wrap around the screen buffer.
// Arguments:
// - The current row.
// Return Value:
// - reference to the previous row
// Note:
// - will throw exception if called with the first row of the text buffer
ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row)
{
int prevRowIndex = Row.GetId() - 1;
if (prevRowIndex < 0)
{
prevRowIndex = TotalRowCount() - 1;
}
THROW_HR_IF(E_FAIL, Row.GetId() == _firstRow);
return _storage.at(prevRowIndex);
}
// Method Description:
// - Retrieves this buffer's current render target.
// Arguments:
@@ -1047,7 +722,7 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep
// - the delimiter class for the given char
const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const
{
return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters);
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
}
// Method Description:
@@ -1650,13 +1325,14 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
if (!cell.DbcsAttr().IsTrailing())
{
selectionText.append(cell.Chars());
const auto chars = cell.Chars();
selectionText.append(chars);
if (copyTextColor)
{
const auto cellData = cell.TextAttr();
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
for (const wchar_t wch : cell.Chars())
for (size_t j = 0; j < chars.size(); ++j)
{
selectionFgAttr.push_back(CellFgAttr);
selectionBkAttr.push_back(CellBkAttr);
@@ -2138,8 +1814,13 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Fetch the row and its "right" which is the last printable character.
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
const CharRow& charRow = row.GetCharRow();
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
short iRight = gsl::narrow_cast<short>(row.MeasureRight());
if (iRight == 0 && !row.WasWrapForced())
{
// don't beef it if iRight=0 on iterator
newBuffer.NewlineCursor();
continue;
}
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
@@ -2174,33 +1855,26 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
}
}
// Loop through every character in the current row (up to
// the "right" boundary, which is one past the final valid
// character)
for (short iOldCol = 0; iOldCol < iRight; iOldCol++)
#if 0
OutputCellIterator it{ oldBuffer.GetCellDataAt({ 0, iOldRow }, Viewport::FromDimensions({ 0, iOldRow }, iRight, 1)) };
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
const auto last = newBuffer.Write(it, preCurPos, true); // true - wrap if we don't fit!
const auto distance = last.GetCellDistance(it);
#endif
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
const auto distance = 5;
if (iOldRow == cOldCursorPos.Y)
{
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
}
try
{
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto glyph = row.GetCharRow().GlyphAt(iOldCol);
const auto dbcsAttr = row.GetCharRow().DbcsAttrAt(iOldCol);
const auto textAttr = row.GetAttrRow().GetAttrByColumn(iOldCol);
if (!newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr))
{
hr = E_OUTOFMEMORY;
break;
}
}
CATCH_RETURN();
cNewCursorPos = preCurPos;
// we started at (0, NewY) -- walk forward by the old position's X to figure out where we land in the new line
newBuffer.GetSize().MoveInBounds(cOldCursorPos.X, cNewCursorPos);
fFoundCursorPos = true;
}
newBuffer.GetSize().MoveInBounds(distance, preCurPos);
newBuffer.GetCursor().SetPosition(preCurPos);
// If we found the old row that the caller was interested in, set the
// out value of that parameter to the cursor's current Y position (the
// new location of the _end_ of that row in the buffer).
@@ -2232,7 +1906,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Only do so if we were not forced to wrap. If we did
// force a word wrap, then the existing line break was
// only because we ran out of space.
if (iRight < cOldColsTotal && !row.WasWrapForced())
if (distance < cOldColsTotal && !row.WasWrapForced())
{
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
@@ -2563,3 +2237,224 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
PointTree result(std::move(intervals));
return result;
}
TextBuffer::WriteResult TextBuffer::FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute)
{
return _WriteRectangular(
region,
[&](WriteResult& result, auto&& at, auto&& size) {
auto& row = GetRowByOffset(at.y());
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
result.columnsWritten += size;
});
}
TextBuffer::WriteResult TextBuffer::FillWithAttributeLinear(til::point at, size_t count, const TextAttribute& attribute)
{
auto out = _WriteLinear(
at,
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count) };
auto& row = GetRowByOffset(at.y());
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
result.columnsWritten += w.RawValue();
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
_NotifyPaint(paint);
return (count -= w.RawValue()) > 0; // keep going if data remains
});
return out;
}
// TODO(DH): make this a member so that IsGlyphFullWidth can be a member ref
static std::tuple<RowMeasurementBuffer, uint16_t> _MeasureStringAndCountColumns(std::wstring_view string) //const noexcept
{
RowMeasurementBuffer measurements;
uint16_t colCount{};
while (!string.empty())
{
auto codepoint = Utf16Parser::ParseNext(string);
string = string.substr(codepoint.size());
uint8_t measurement{ IsGlyphFullWidth(codepoint) ? 2u : 1u };
measurements.replace(measurements.size(), measurements.size(), { measurement, gsl::narrow_cast<uint16_t>(1) });
if (codepoint.size() > 1)
{
measurements.replace(measurements.size(), measurements.size(), typename decltype(measurements)::rle_type{ uint8_t{ 0 }, gsl::narrow_cast<uint16_t>(codepoint.size() - 1) });
}
colCount += measurement;
}
return { std::move(measurements), colCount };
}
struct no_attributes
{
};
template<typename T>
TextBuffer::WriteResult TextBuffer::_WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, [[maybe_unused]] const T& attributes)
{
uint16_t colCount = std::accumulate(
measurements.runs().cbegin(),
measurements.runs().cend(),
uint16_t{},
[](auto&& v, auto&& r) -> uint16_t { return v + (r.value * r.length); });
const auto out = _WriteLinear(
at,
[&](WriteResult& result, auto&& at, auto&& /* size */) {
auto& row = GetRowByOffset(at.y());
const auto [consumedWchar, consumedDestCols, consumedSourceCols] = row.WriteStringAtMeasured(at.x<uint16_t>(), colCount, string, measurements);
if constexpr (std::is_same<T, TextAttribute>::value)
{
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedDestCols), attributes);
}
#if 0
else if constexpr (std::is_same<T, ATTR_ROW::rle_vector>::value)
{
// user has passed us a packed representation of attributes
// It is a programming error to pass one whose total length does
// not match the measurement buffer
const auto attrRunSlice{ attributes.slice(0, consumedSourceCols) };
attrRunSlice.resize_trailing_extent(consumedDestCols); // this may be very wrong. TODO(DH)
// intent: I believe that this will take the attribute from the cell before the one we had to cut off
// and then extend it to cover our empty column?
row.GetAttrRow()._data.replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedSourceCols), attrRunSlice.runs());
}
#endif
const Viewport paint = Viewport::FromDimensions(at, { ::base::saturated_cast<SHORT>(consumedDestCols), 1 });
_NotifyPaint(paint);
colCount -= consumedSourceCols;
result.charactersRead += consumedWchar;
result.charactersWritten += consumedWchar;
result.columnsWritten += consumedDestCols;
result.columnsRead += consumedSourceCols;
// unchecked substr
//string = std::wstring_view{ &*string.begin() + consumedWchar, string.size() - consumedWchar };
//string = til::unchecked_substr(string, consumedWchar);
string = string.substr(consumedWchar);
measurements = measurements.slice(gsl::narrow_cast<uint16_t>(consumedWchar), measurements.size());
return !string.empty();
});
return out;
}
TextBuffer::WriteResult TextBuffer::WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements)
{
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), _currentAttributes);
}
TextBuffer::WriteResult TextBuffer::WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr)
{
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), attr);
}
TextBuffer::WriteResult TextBuffer::WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string)
{
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), no_attributes{});
}
template<typename T>
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, [[maybe_unused]] const T& attr)
{
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
auto out = _WriteRectangular(
region,
[&](WriteResult& /* result */, auto&& at, auto&& size) {
auto& row = GetRowByOffset(at.y());
row.Fill(region.left(), size / width, character, width);
if constexpr (std::is_same<T, TextAttribute>::value)
{
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attr);
}
});
return out;
}
template<typename T>
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, [[maybe_unused]] const T& attr)
{
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
return _WriteLinear(
at,
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
// size is how many *columns* we have left to fill, k?
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count * width) };
auto& row = GetRowByOffset(at.y());
const auto consumedWchars = row.Fill(at.x(), count, character, width);
if constexpr (std::is_same<T, TextAttribute>::value)
{
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), w), attr);
}
result.charactersWritten += consumedWchars;
result.columnsWritten += w.RawValue();
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
_NotifyPaint(paint);
return (count -= consumedWchars) > 0; // keep going if data remains
});
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character)
{
return _FillWithCharacterAndAttributeRectangular(region, character, no_attributes{});
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr)
{
return _FillWithCharacterAndAttributeRectangular(region, character, attr);
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character)
{
return _FillWithCharacterAndAttributeLinear(at, count, character, no_attributes{});
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr)
{
return _FillWithCharacterAndAttributeLinear(at, count, character, attr);
}
TextBuffer::WriteResult TextBuffer::WriteRowImage(const til::point at, const RowImage& ri)
{
auto& row{ GetRowByOffset(at.y()) };
row.Reinsert(at.x<uint16_t>(), ri);
return {};
}
template<typename TLambda>
TextBuffer::WriteResult TextBuffer::_WriteRectangular(const til::rectangle& rect, TLambda&& thing)
{
WriteResult out{};
for (auto y{ rect.top() }; y < rect.bottom(); ++y)
{
thing(out, til::point{ rect.left(), y }, rect.width());
}
_NotifyPaint(Viewport::FromDimensions(rect.origin(), rect.size()));
return out;
}
template<typename TLambda>
TextBuffer::WriteResult TextBuffer::_WriteLinear(const til::point& start, TLambda&& thing)
{
auto point{ start };
WriteResult out{};
while (_size.IsInBounds(point) && thing(out, point, _size.Width() - point.x()))
{
point = { 0, point.y() };
};
return out;
}

View File

@@ -54,7 +54,6 @@ filling in the last row, and updating the screen.
#include "cursor.h"
#include "Row.hpp"
#include "TextAttribute.hpp"
#include "UnicodeStorage.hpp"
#include "../types/inc/Viewport.hpp"
#include "../buffer/out/textBufferCellIterator.hpp"
@@ -86,19 +85,6 @@ public:
TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const;
// Text insertion functions
OutputCellIterator Write(const OutputCellIterator givenIt);
OutputCellIterator Write(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> wrap = true);
OutputCellIterator WriteLine(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> setWrap = std::nullopt,
const std::optional<size_t> limitRight = std::nullopt);
bool InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
bool InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
bool IncrementCursor();
bool NewlineCursor();
@@ -136,9 +122,6 @@ public:
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
UnicodeStorage& GetUnicodeStorage() noexcept;
Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const;
@@ -200,6 +183,30 @@ public:
void CopyPatterns(const TextBuffer& OtherBuffer);
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const size_t firstRow, const size_t lastRow) const;
struct WriteResult
{
size_t charactersWritten;
size_t charactersRead;
size_t columnsWritten;
size_t columnsRead;
};
WriteResult WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements);
WriteResult WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr);
WriteResult WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string);
WriteResult FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character);
WriteResult FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character);
WriteResult FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr);
WriteResult FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr);
WriteResult FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute);
WriteResult FillWithAttributeLinear(const til::point at, size_t count, const TextAttribute& attribute);
WriteResult WriteRowImage(const til::point at, const RowImage& ri);
private:
void _UpdateSize();
Microsoft::Console::Types::Viewport _size;
@@ -210,9 +217,6 @@ private:
TextAttribute _currentAttributes;
// storage location for glyphs that can't fit into the buffer normally
UnicodeStorage _unicodeStorage;
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
uint16_t _currentHyperlinkId;
@@ -230,12 +234,7 @@ private:
void _NotifyPaint(const Microsoft::Console::Types::Viewport& viewport) const;
// Assist with maintaining proper buffer state for Double Byte character sequences
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
ROW& _GetFirstRow();
ROW& _GetPrevRowNoWrap(const ROW& row);
void _ExpandTextRow(SMALL_RECT& selectionRow) const;
@@ -247,6 +246,21 @@ private:
void _PruneHyperlinks();
template<typename TLambda>
WriteResult _WriteRectangular(const til::rectangle& rect, TLambda&& thing);
template<typename TLambda>
WriteResult _WriteLinear(const til::point& start, TLambda&& thing);
template<typename T>
WriteResult _FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const T& attr);
template<typename T>
WriteResult _FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const T& attr);
template<typename T>
WriteResult _WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, const T& attributes);
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
size_t _currentPatternId;

View File

@@ -5,7 +5,6 @@
#include "textBufferCellIterator.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/viewport.hpp"
@@ -94,20 +93,92 @@ bool TextBufferCellIterator::operator!=(const TextBufferCellIterator& it) const
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& movement)
{
// Note that this method is called intensively when the terminal is under heavy load.
// We need to aggressively optimize it, comparing to the -= operator.
ptrdiff_t move = movement;
auto newPos = _pos;
while (move > 0 && !_exceeded)
if (move < 0)
{
_exceeded = !_bounds.IncrementInBounds(newPos);
// Early branching to leave the rare case to -= operator.
// This helps reducing the instruction count within this method, which is good for instruction cache.
return *this -= -move;
}
// The remaining code in this function is functionally equivalent to:
// auto newPos = _pos;
// while (move > 0 && !_exceeded)
// {
// _exceeded = !_bounds.IncrementInBounds(newPos);
// move--;
// }
// _SetPos(newPos);
//
// _SetPos() necessitates calling _GenerateView() and thus the construction
// of a new OutputCellView(). This has a high performance impact (ICache spill?).
// The code below inlines _bounds.IncrementInBounds as well as SetPos.
// In the hot path (_pos.Y doesn't change) we modify the _view directly.
// Hoist these integers which will be used frequently later.
const auto boundsRightInclusive = _bounds.RightInclusive();
const auto boundsLeft = _bounds.Left();
const auto boundsBottomInclusive = _bounds.BottomInclusive();
const auto boundsTop = _bounds.Top();
const auto oldX = _pos.X;
const auto oldY = _pos.Y;
// Under MSVC writing the individual members of a COORD generates worse assembly
// compared to having them be local variables. This causes a performance impact.
auto newX = oldX;
auto newY = oldY;
while (move > 0)
{
if (newX == boundsRightInclusive)
{
newX = boundsLeft;
newY++;
if (newY > boundsBottomInclusive)
{
newY = boundsTop;
_exceeded = true;
break;
}
}
else
{
newX++;
_exceeded = false;
}
move--;
}
while (move < 0 && !_exceeded)
if (_exceeded)
{
_exceeded = !_bounds.DecrementInBounds(newPos);
move++;
// Early return because nothing needs to be done here.
return *this;
}
_SetPos(newPos);
return (*this);
if (newY == oldY)
{
// hot path
const auto diff = gsl::narrow_cast<ptrdiff_t>(newX) - gsl::narrow_cast<ptrdiff_t>(oldX);
_attrIter += diff;
_view.UpdateTextAttribute(*_attrIter);
_view.UpdateText(_pRow->GlyphAt(newX));
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
_pos.X = newX;
}
else
{
// cold path (_GenerateView is slow)
_pRow = s_GetRow(_buffer, { newX, newY });
_attrIter = _pRow->GetAttrRow().cbegin() + newX;
_pos.X = newX;
_pos.Y = newY;
_GenerateView();
}
return *this;
}
// Routine Description:
@@ -118,7 +189,22 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator-=(const ptrdiff_t& movement)
{
return this->operator+=(-movement);
ptrdiff_t move = movement;
if (move < 0)
{
return (*this) += (-move);
}
auto newPos = _pos;
while (move > 0 && !_exceeded)
{
_exceeded = !_bounds.DecrementInBounds(newPos);
move--;
}
_SetPos(newPos);
_GenerateView();
return (*this);
}
// Routine Description:
@@ -238,8 +324,8 @@ const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const COOR
// - Updates the internal view. Call after updating row, attribute, or positions.
void TextBufferCellIterator::_GenerateView()
{
_view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X),
_pRow->GetCharRow().DbcsAttrAt(_pos.X),
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
_pRow->DbcsAttrAt(_pos.X),
*_attrIter,
TextAttributeBehavior::Stored);
}

View File

@@ -15,12 +15,12 @@ Author(s):
#pragma once
#include "CharRow.hpp"
#include "AttrRow.hpp"
#include "OutputCellView.hpp"
#include "../../types/inc/viewport.hpp"
class TextBuffer;
class ROW;
class TextBufferCellIterator
{
@@ -58,8 +58,8 @@ protected:
const ROW* _pRow;
ATTR_ROW::const_iterator _attrIter;
const TextBuffer& _buffer;
const Microsoft::Console::Types::Viewport _bounds;
std::reference_wrapper<const TextBuffer> _buffer;
Microsoft::Console::Types::Viewport _bounds;
bool _exceeded;
COORD _pos;

View File

@@ -5,7 +5,6 @@
#include "textBufferTextIterator.hpp"
#include "CharRow.hpp"
#include "Row.hpp"
#pragma hdrstop

View File

@@ -1971,9 +1971,9 @@ namespace SettingsModelLocalTests
auto settings = implementation::CascadiaSettings::FromJson(settingsObject);
VERIFY_ARE_EQUAL(3u, settings->_globals->_actionMap->_KeyMap.size());
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('a') }));
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('b') }));
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }));
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }));
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
for (const auto& warning : settings->_globals->_keybindingsWarnings)
{
@@ -2124,7 +2124,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(1u, nameMap.Size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -2141,7 +2141,7 @@ namespace SettingsModelLocalTests
Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set.");
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -2155,7 +2155,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -2169,7 +2169,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -2183,7 +2183,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -2841,7 +2841,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(1u, nameMap.Size());
const KeyChord expectedKeyChord{ true, false, true, static_cast<int>('W') };
const KeyChord expectedKeyChord{ true, false, true, false, static_cast<int>('W'), 0 };
{
// Verify NameMap returns correct value
const auto& cmd{ nameMap.TryLookup(L"foo") };

View File

@@ -36,6 +36,7 @@ namespace SettingsModelLocalTests
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(KeyChords);
TEST_METHOD(ManyKeysSameAction);
TEST_METHOD(LayerKeybindings);
TEST_METHOD(UnbindKeybindings);
@@ -61,6 +62,59 @@ namespace SettingsModelLocalTests
}
};
void KeyBindingsTests::KeyChords()
{
struct testCase
{
VirtualKeyModifiers modifiers;
int32_t vkey;
int32_t scanCode;
std::wstring_view expected;
};
static constexpr std::array testCases{
testCase{
VirtualKeyModifiers::None,
'A',
0,
L"a",
},
testCase{
VirtualKeyModifiers::Control,
'A',
0,
L"ctrl+a",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift,
VK_OEM_PLUS,
0,
L"ctrl+shift+plus",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
255,
0,
L"ctrl+shift+alt+win+vk(255)",
},
testCase{
VirtualKeyModifiers::Windows,
0,
123,
L"ctrl+shift+alt+win+sc(123)",
},
};
for (const auto& tc : testCases)
{
KeyChord expectedKeyChord{ tc.modifiers, tc.vkey, tc.scanCode };
const auto actualString = KeyChordSerialization::ToString(expectedKeyChord);
VERIFY_ARE_EQUAL(tc.expected, actualString);
const auto actualKeyChord = KeyChordSerialization::FromString(actualString);
VERIFY_ARE_EQUAL(expectedKeyChord, actualKeyChord);
}
}
void KeyBindingsTests::ManyKeysSameAction()
{
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
@@ -75,7 +129,6 @@ namespace SettingsModelLocalTests
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
@@ -99,7 +152,6 @@ namespace SettingsModelLocalTests
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
@@ -129,7 +181,6 @@ namespace SettingsModelLocalTests
const auto bindings5Json = VerifyParseSucceeded(bindings5String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
@@ -142,7 +193,7 @@ namespace SettingsModelLocalTests
L"Try unbinding a key using `\"unbound\"` to unbind the key"));
actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using `null` to unbind the key"));
@@ -152,7 +203,7 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting
actionMap->LayerJson(bindings3Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using an unrecognized command to unbind the key"));
@@ -162,7 +213,7 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting
actionMap->LayerJson(bindings4Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using a straight up invalid value to unbind the key"));
@@ -172,13 +223,13 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting
actionMap->LayerJson(bindings5Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key that wasn't bound at all"));
actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
}
void KeyBindingsTests::TestArbitraryArgs()
@@ -203,7 +254,6 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size());
@@ -211,10 +261,9 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` without args parses as Copy(SingleLine=false)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -222,10 +271,9 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('C') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -233,10 +281,9 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ false, true, true, static_cast<int32_t>('C') };
KeyChord kc{ false, true, true, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.SingleLine());
}
@@ -244,11 +291,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` without args parses as NewTab(Index=null)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('T') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('T'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NULL(realArgs.TerminalArgs().ProfileIndex());
@@ -256,11 +302,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` parses args correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('T') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('T'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
@@ -270,11 +315,10 @@ namespace SettingsModelLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` with an index greater than the legacy "
L"args afforded parses correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('Y') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('Y'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
@@ -284,11 +328,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` ignores args it doesn't understand"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -296,11 +339,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` null as it's `args` parses as the default option"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -308,11 +350,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a positive delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(1, realArgs.Delta());
}
@@ -320,11 +361,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a negative delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(-1, realArgs.Delta());
}
@@ -342,44 +382,39 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
@@ -396,37 +431,33 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TabColor());
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
VERIFY_ARE_EQUAL(til::color(0x563412), til::color(realArgs.TabColor().Value()));
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
@@ -441,16 +472,14 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -470,63 +499,56 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size());
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
}
{
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
@@ -535,7 +557,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
}
@@ -551,26 +572,23 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Forward);
}
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Backward);
}
@@ -584,7 +602,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
}
@@ -601,35 +618,31 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
}
{
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::CommandLine);
}
@@ -637,7 +650,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
}
@@ -669,7 +681,6 @@ namespace SettingsModelLocalTests
};
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
{
@@ -677,7 +688,7 @@ namespace SettingsModelLocalTests
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }, kbd);
}
{
Log::Comment(L"command with args");
@@ -688,7 +699,7 @@ namespace SettingsModelLocalTests
args->SingleLine(true);
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }, kbd);
}
{
Log::Comment(L"command with new terminal args");
@@ -700,7 +711,7 @@ namespace SettingsModelLocalTests
auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) };
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }, kbd);
}
{
Log::Comment(L"command with hidden args");
@@ -708,7 +719,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P'), 0 }, kbd);
}
}
}

View File

@@ -113,7 +113,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -134,7 +134,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -156,7 +156,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -178,7 +178,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -200,7 +200,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -222,7 +222,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -245,7 +245,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -265,7 +265,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -287,7 +287,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('I'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -310,7 +310,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('J') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('J'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -332,7 +332,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('K') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('K'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -355,7 +355,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('L') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('L'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();

View File

@@ -56,6 +56,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(ParseComboCommandlineIntoArgs);
TEST_METHOD(ParseFocusTabArgs);
TEST_METHOD(ParseMoveFocusArgs);
TEST_METHOD(ParseMovePaneArgs);
TEST_METHOD(ParseArgumentsWithParsingTerminators);
TEST_METHOD(ParseFocusPaneArgs);
@@ -1207,6 +1208,124 @@ namespace TerminalAppLocalTests
}
}
void CommandlineTest::ParseMovePaneArgs()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
END_TEST_METHOD_PROPERTIES()
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `mp` instead of `move-pane`");
const wchar_t* subcommand = useShortForm ? L"mp" : L"move-pane";
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
Log::Comment(NoThrowString().Format(
L"Just the subcommand, without a direction, should fail."));
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"up" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"down" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"badDirection" };
Log::Comment(NoThrowString().Format(
L"move-pane with an invalid direction should fail."));
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left", L";", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
actionAndArgs = appArgs._startupActions.at(2);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}
}
void CommandlineTest::ParseFocusPaneArgs()
{
BEGIN_TEST_METHOD_PROPERTIES()

View File

@@ -82,6 +82,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(MoveFocusFromZoomedPane);
TEST_METHOD(CloseZoomedPane);
TEST_METHOD(MovePanes);
TEST_METHOD(NextMRUTab);
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);
@@ -93,6 +95,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestPreviewDismissScheme);
TEST_METHOD(TestPreviewSchemeWhilePreviewing);
TEST_METHOD(TestClampSwitchToTab);
TEST_CLASS_SETUP(ClassSetup)
{
return true;
@@ -817,6 +821,212 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::MovePanes()
{
auto page = _commonSetup();
Log::Comment(L"Setup 4 panes.");
// Create the following layout
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0;
TestOnUIThread([&]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
firstId = tab->_activePane->Id().value();
// We start with 1 tab, split vertically to get
// -------------------
// | 1 | 2 |
// | | |
// -------------------
page->_SplitPane(SplitState::Vertical, SplitType::Duplicate, 0.5f, nullptr);
secondId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `2` pane is focused, go back to `1` being focused
page->_MoveFocus(FocusDirection::Left);
});
Sleep(250);
TestOnUIThread([&]() {
// Split again to make the 3rd tab
// -------------------
// | 1 | |
// | | |
// ---------| 2 |
// | 3 | |
// | | |
// -------------------
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
// Split again to make the 3rd tab
thirdId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `3` pane is focused, go back to `2` being focused
page->_MoveFocus(FocusDirection::Right);
});
Sleep(250);
TestOnUIThread([&]() {
// Split to create the final pane
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
fourthId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// just to be complete, make sure we actually have 4 different ids
VERIFY_ARE_NOT_EQUAL(firstId, fourthId);
VERIFY_ARE_NOT_EQUAL(secondId, fourthId);
VERIFY_ARE_NOT_EQUAL(thirdId, fourthId);
VERIFY_ARE_NOT_EQUAL(firstId, thirdId);
VERIFY_ARE_NOT_EQUAL(secondId, thirdId);
VERIFY_ARE_NOT_EQUAL(firstId, secondId);
});
// Gratuitous use of sleep to make sure that the UI has updated properly
// after each operation.
Sleep(250);
// Now try to move the pane through the tree
Log::Comment(L"Move pane to the left. This should swap panes 3 and 4");
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 4 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Left };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_secondChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane to up. This should swap panes 1 and 4");
// -------------------
// | 4 | 2 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Up };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(firstId, tab->_rootPane->_firstChild->_secondChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane to the right. This should swap panes 2 and 4");
// -------------------
// | 2 | 4 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Right };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(secondId, tab->_rootPane->_firstChild->_firstChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane down. This should swap panes 3 and 4");
// -------------------
// | 2 | 3 |
// | | |
// -------------------
// | 1 | 4 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Down };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_firstChild->Id().value());
});
}
void TabTests::NextMRUTab()
{
// This is a test for GH#8025 - we want to make sure that we can do both
@@ -1342,4 +1552,55 @@ namespace TerminalAppLocalTests
});
}
void TabTests::TestClampSwitchToTab()
{
Log::Comment(L"Test that switching to a tab index higher than the number of tabs just clamps to the last tab.");
auto page = _commonSetup();
VERIFY_IS_NOT_NULL(page);
Log::Comment(L"Create a second tab");
TestOnUIThread([&page]() {
NewTerminalArgs newTerminalArgs{ 1 };
page->_OpenNewTab(newTerminalArgs);
});
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
Log::Comment(L"Create a third tab");
TestOnUIThread([&page]() {
NewTerminalArgs newTerminalArgs{ 2 };
page->_OpenNewTab(newTerminalArgs);
});
VERIFY_ARE_EQUAL(3u, page->_tabs.Size());
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
});
TestOnUIThread([&page]() {
Log::Comment(L"Switch to the first tab");
page->_SelectTab(0);
});
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(0u, focusedTabIndexOpt.value());
});
TestOnUIThread([&page]() {
Log::Comment(L"Switch to the tab 6, which is greater than number of tabs. This should switch to the third tab");
page->_SelectTab(6);
});
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
});
}
}

View File

@@ -172,7 +172,6 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8 },
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
_uiaProvider{ nullptr },
_uiaProviderInitialized{ false },
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
_pfnWriteCallback{ nullptr },
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
@@ -324,26 +323,22 @@ void HwndTerminal::_UpdateFont(int newDpi)
IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
{
if (nullptr == _uiaProvider && !_uiaProviderInitialized)
// If TermControlUiaProvider throws during construction,
// we don't want to try constructing an instance again and again.
// _uiaProviderInitialized helps us prevent this.
if (!_uiaProviderInitialized)
{
std::unique_lock<std::shared_mutex> lock;
try
{
#pragma warning(suppress : 26441) // The lock is named, this appears to be a false positive
lock = _terminal->LockForWriting();
if (_uiaProviderInitialized)
{
return _uiaProvider.Get();
}
auto lock = _terminal->LockForWriting();
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, this->GetUiaData(), this));
_uiaProviderInitialized = true;
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
_uiaProvider = nullptr;
}
_uiaProviderInitialized = true;
}
return _uiaProvider.Get();

View File

@@ -73,7 +73,6 @@ private:
FontInfoDesired _desiredFont;
FontInfo _actualFont;
int _currentDpi;
bool _uiaProviderInitialized;
std::function<void(wchar_t*)> _pfnWriteCallback;
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
@@ -83,6 +82,7 @@ private:
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
bool _focused{ false };
bool _uiaProviderInitialized{ false };
std::chrono::milliseconds _multiClickTime;
unsigned int _multiClickCounter{};

View File

@@ -313,6 +313,24 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
{
if (realArgs.Direction() == FocusDirection::None)
{
// Do nothing
args.Handled(false);
}
else
{
_MovePane(realArgs.Direction());
args.Handled(true);
}
}
}
void TerminalPage::_HandleCopyText(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View File

@@ -192,6 +192,7 @@ void AppCommandlineArgs::_buildParser()
_buildSplitPaneParser();
_buildFocusTabParser();
_buildMoveFocusParser();
_buildMovePaneParser();
_buildFocusPaneParser();
}
@@ -398,6 +399,54 @@ void AppCommandlineArgs::_buildMoveFocusParser()
setupSubcommand(_moveFocusShort);
}
// Method Description:
// - Adds the `move-pane` subcommand and related options to the commandline parser.
// - Additionally adds the `mp` subcommand, which is just a shortened version of `move-pane`
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildMovePaneParser()
{
_movePaneCommand = _app.add_subcommand("move-pane", RS_A(L"CmdMovePaneDesc"));
_movePaneShort = _app.add_subcommand("mp", RS_A(L"CmdMPDesc"));
auto setupSubcommand = [this](auto* subcommand) {
std::map<std::string, FocusDirection> map = {
{ "left", FocusDirection::Left },
{ "right", FocusDirection::Right },
{ "up", FocusDirection::Up },
{ "down", FocusDirection::Down }
};
auto* directionOpt = subcommand->add_option("direction",
_movePaneDirection,
RS_A(L"CmdMovePaneDirectionArgDesc"));
directionOpt->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
directionOpt->required();
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
if (_movePaneDirection != FocusDirection::None)
{
MovePaneArgs args{ _movePaneDirection };
ActionAndArgs actionAndArgs{};
actionAndArgs.Action(ShortcutAction::MovePane);
actionAndArgs.Args(args);
_startupActions.push_back(std::move(actionAndArgs));
}
});
};
setupSubcommand(_movePaneCommand);
setupSubcommand(_movePaneShort);
}
// Method Description:
// - Adds the `focus-pane` subcommand and related options to the commandline parser.
// - Additionally adds the `fp` subcommand, which is just a shortened version of `focus-pane`
@@ -574,6 +623,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
*_focusTabShort ||
*_moveFocusCommand ||
*_moveFocusShort ||
*_movePaneCommand ||
*_movePaneShort ||
*_focusPaneCommand ||
*_focusPaneShort ||
*_newPaneShort.subcommand ||
@@ -607,6 +658,7 @@ void AppCommandlineArgs::_resetStateToDefault()
_focusPrevTab = false;
_moveFocusDirection = FocusDirection::None;
_movePaneDirection = FocusDirection::None;
_focusPaneTarget = -1;

View File

@@ -82,6 +82,8 @@ private:
CLI::App* _focusTabShort;
CLI::App* _moveFocusCommand;
CLI::App* _moveFocusShort;
CLI::App* _movePaneCommand;
CLI::App* _movePaneShort;
CLI::App* _focusPaneCommand;
CLI::App* _focusPaneShort;
@@ -95,6 +97,7 @@ private:
bool _suppressApplicationTitle{ false };
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _movePaneDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
// _commandline will contain the command line with which we'll be spawning a new terminal
std::vector<std::string> _commandline;
@@ -128,6 +131,7 @@ private:
void _buildSplitPaneParser();
void _buildFocusTabParser();
void _buildMoveFocusParser();
void _buildMovePaneParser();
void _buildFocusPaneParser();
bool _noCommandsProvided();
void _resetStateToDefault();

View File

@@ -1129,7 +1129,7 @@ namespace winrt::TerminalApp::implementation
// - Gets the taskbar state value from the last active control
// Return Value:
// - The taskbar state of the last active control
size_t AppLogic::GetLastActiveControlTaskbarState()
uint64_t AppLogic::GetLastActiveControlTaskbarState()
{
if (_root)
{
@@ -1142,7 +1142,7 @@ namespace winrt::TerminalApp::implementation
// - Gets the taskbar progress value from the last active control
// Return Value:
// - The taskbar progress of the last active control
size_t AppLogic::GetLastActiveControlTaskbarProgress()
uint64_t AppLogic::GetLastActiveControlTaskbarProgress()
{
if (_root)
{

View File

@@ -89,8 +89,8 @@ namespace winrt::TerminalApp::implementation
void WindowCloseButtonClicked();
size_t GetLastActiveControlTaskbarState();
size_t GetLastActiveControlTaskbarProgress();
uint64_t GetLastActiveControlTaskbarState();
uint64_t GetLastActiveControlTaskbarProgress();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);

View File

@@ -63,23 +63,15 @@ namespace winrt::TerminalApp::implementation
// - <none>
void ColorPickupFlyout::ShowColorPickerButton_Click(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&)
{
auto targetType = this->FlyoutPresenterStyle().TargetType();
auto s = Windows::UI::Xaml::Style{};
s.TargetType(targetType);
auto visibility = customColorPanel().Visibility();
if (visibility == winrt::Windows::UI::Xaml::Visibility::Collapsed)
{
customColorPanel().Visibility(winrt::Windows::UI::Xaml::Visibility::Visible);
auto setter = Windows::UI::Xaml::Setter(Windows::UI::Xaml::FrameworkElement::MinWidthProperty(), winrt::box_value(540));
s.Setters().Append(setter);
}
else
{
customColorPanel().Visibility(winrt::Windows::UI::Xaml::Visibility::Collapsed);
auto setter = Windows::UI::Xaml::Setter(Windows::UI::Xaml::FrameworkElement::MinWidthProperty(), winrt::box_value(0));
s.Setters().Append(setter);
}
this->FlyoutPresenterStyle(s);
}
// Method Description:

View File

@@ -250,10 +250,12 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
const auto key = e.OriginalKey();
const auto scanCode = e.KeyStatus().ScanCode;
const auto coreWindow = CoreWindow::GetForCurrentThread();
const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
// Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because
// they're not considered input key presses. While they don't raise KeyDown events,
@@ -264,7 +266,7 @@ namespace winrt::TerminalApp::implementation
// a really widely used keyboard navigation key.
if (_currentMode == CommandPaletteMode::TabSwitchMode && _actionMap)
{
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, false, static_cast<int32_t>(key), static_cast<int32_t>(scanCode) };
if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) })
{
if (cmd.ActionAndArgs().Action() == ShortcutAction::PrevTab)
@@ -402,9 +404,10 @@ namespace winrt::TerminalApp::implementation
// - <none>
void CommandPalette::_anchorKeyUpHandler()
{
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
const auto coreWindow = CoreWindow::GetForCurrentThread();
const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
if (!ctrlDown && !altDown && !shiftDown)
{

View File

@@ -19,6 +19,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
_wrappedConnection{ std::move(wrappedConnection) }
{
}
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) {}
~DebugInputTapConnection() = default;
void Start()
{

View File

@@ -13,6 +13,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
{
public:
explicit DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection);
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/){};
~DebugTapConnection();
void Start();
void WriteInput(hstring const& data);

View File

@@ -213,41 +213,6 @@ bool Pane::ResizePane(const ResizeDirection& direction)
return false;
}
// Method Description:
// - Attempts to handle moving focus to one of our children. If our split
// direction isn't appropriate for the move direction, then we'll return
// false, to try and let our parent handle the move. If our child we'd move
// focus to is already focused, we'll also return false, to again let our
// parent try and handle the focus movement.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - true if we handled this focus move request.
bool Pane::_NavigateFocus(const FocusDirection& direction)
{
if (!DirectionMatchesSplit(direction, _splitState))
{
return false;
}
const bool focusSecond = (direction == FocusDirection::Right) || (direction == FocusDirection::Down);
const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild;
// If the child we want to move focus to is _already_ focused, return false,
// to try and let our parent figure it out.
if (newlyFocusedChild->_HasFocusedChild())
{
return false;
}
// Transfer focus to our child, and update the focus of our tree.
newlyFocusedChild->_FocusFirstChild();
UpdateVisuals();
return true;
}
// Method Description:
// - Attempts to move focus to one of our children. If we have a focused child,
// we'll try to move the focus in the direction requested.
@@ -255,8 +220,8 @@ bool Pane::_NavigateFocus(const FocusDirection& direction)
// direction, we'll return false. This will indicate to our parent that they
// should try and move the focus themselves. In this way, the focus can move
// up and down the tree to the correct pane.
// - This method is _very_ similar to ResizePane. Both are trying to find the
// right separator to move (focus) in a direction.
// - This method is _very_ similar to MovePane. Both are trying to find the
// right pane to move (focus) in a direction.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
@@ -270,33 +235,400 @@ bool Pane::NavigateFocus(const FocusDirection& direction)
return false;
}
// Check if either our first or second child is the currently focused leaf.
// If it is, and the requested move direction matches our separator, then
// we're the pane that needs to handle this focus move.
const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastActive;
const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive;
if (firstIsFocused || secondIsFocused)
// If the focus direction does not match the split direction, the focused pane
// and its neighbor must necessarily be contained within the same child.
if (!DirectionMatchesSplit(direction, _splitState))
{
return _NavigateFocus(direction);
return _firstChild->NavigateFocus(direction) || _secondChild->NavigateFocus(direction);
}
// If neither of our children were the focused leaf, then recurse into
// our children and see if they can handle the focus move.
// For each child, if it has a focused descendant, try having that child
// handle the focus move.
// If the child wasn't able to handle the focus move, it's possible that
// there were no descendants with a separator the correct direction. If
// our separator _is_ the correct direction, then we should be the pane
// to move focus into our other child. Otherwise, just return false, as
// we couldn't handle it either.
if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild())
// Since the direction is the same as our split, it is possible that we must
// move focus from from one child to another child.
// We now must keep track of state while we recurse.
auto focusNeighborPair = _FindFocusAndNeighbor(direction, { 0, 0 });
// Once we have found the focused pane and its neighbor, wherever they may
// be we can update the focus.
if (focusNeighborPair.focus && focusNeighborPair.neighbor)
{
return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction);
focusNeighborPair.neighbor->_FocusFirstChild();
return true;
}
if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
return false;
}
// Method Description:
// - Attempts to find the parent pane of the provided pane.
// Arguments:
// - pane: The pane to search for.
// Return Value:
// - the parent of `pane` if pane is in this tree.
std::shared_ptr<Pane> Pane::_FindParentOfPane(const std::shared_ptr<Pane> pane)
{
if (_IsLeaf())
{
return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction);
return nullptr;
}
if (_firstChild == pane || _secondChild == pane)
{
return shared_from_this();
}
if (auto p = _firstChild->_FindParentOfPane(pane))
{
return p;
}
return _secondChild->_FindParentOfPane(pane);
}
// Method Description:
// - Attempts to swap the location of the two given panes in the tree.
// Searches the tree starting at this pane to find the parent pane for each of
// the arguments, and if both parents are found, replaces the appropriate
// child in each.
// Arguments:
// - first: A pointer to the first pane to switch.
// - second: A pointer to the second pane to switch.
// Return Value:
// - true if a swap was performed.
bool Pane::SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second)
{
// If there is nothing to swap, just return.
if (first == second || _IsLeaf())
{
return false;
}
std::unique_lock lock{ _createCloseLock };
// Recurse through the tree to find the parent panes of each pane that is
// being swapped.
std::shared_ptr<Pane> firstParent = _FindParentOfPane(first);
std::shared_ptr<Pane> secondParent = _FindParentOfPane(second);
// We should have found either no elements, or both elements.
// If we only found one parent then the pane SwapPane was called on did not
// contain both panes as leaves, as could happen if the tree was modified
// after the pointers were found but before we reached this function.
if (firstParent && secondParent)
{
// Swap size/display information of the two panes.
std::swap(first->_borders, second->_borders);
// Replace the old child with new one, and revoke appropriate event
// handlers.
auto replaceChild = [](auto& parent, auto oldChild, auto newChild) {
// Revoke the old handlers
if (parent->_firstChild == oldChild)
{
parent->_firstChild->Closed(parent->_firstClosedToken);
parent->_firstChild = newChild;
}
else if (parent->_secondChild == oldChild)
{
parent->_secondChild->Closed(parent->_secondClosedToken);
parent->_secondChild = newChild;
}
// Clear now to ensure that we can add the child's grid to us later
parent->_root.Children().Clear();
};
// Make sure that the right event handlers are set, and the children
// are placed in the appropriate locations in the grid.
auto updateParent = [](auto& parent) {
parent->_SetupChildCloseHandlers();
parent->_root.Children().Clear();
parent->_root.Children().Append(parent->_firstChild->GetRootElement());
parent->_root.Children().Append(parent->_secondChild->GetRootElement());
// Make sure they have the correct borders, and also that they are
// placed in the right location in the grid.
// This mildly reproduces ApplySplitDefinitions, but is different in
// that it does not want to utilize the parent's border to set child
// borders.
if (parent->_splitState == SplitState::Vertical)
{
Controls::Grid::SetColumn(parent->_firstChild->GetRootElement(), 0);
Controls::Grid::SetColumn(parent->_secondChild->GetRootElement(), 1);
}
else if (parent->_splitState == SplitState::Horizontal)
{
Controls::Grid::SetRow(parent->_firstChild->GetRootElement(), 0);
Controls::Grid::SetRow(parent->_secondChild->GetRootElement(), 1);
}
parent->_firstChild->_UpdateBorders();
parent->_secondChild->_UpdateBorders();
};
// If the firstParent and secondParent are the same, then we are just
// swapping the first child and second child of that parent.
if (firstParent == secondParent)
{
firstParent->_firstChild->Closed(firstParent->_firstClosedToken);
firstParent->_secondChild->Closed(firstParent->_secondClosedToken);
std::swap(firstParent->_firstChild, firstParent->_secondChild);
updateParent(firstParent);
}
else
{
// Replace both children before updating display to ensure
// that the grid elements are not attached to multiple panes
replaceChild(firstParent, first, second);
replaceChild(secondParent, second, first);
updateParent(firstParent);
updateParent(secondParent);
}
return true;
}
return false;
}
// Method Description:
// - Given two panes, test whether the `direction` side of first is adjacent to second.
// Arguments:
// - first: The reference pane.
// - second: the pane to test adjacency with.
// - direction: The direction to search in from the reference pane.
// Return Value:
// - true if the two panes are adjacent.
bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
const Pane::PanePoint firstOffset,
const std::shared_ptr<Pane> second,
const Pane::PanePoint secondOffset,
const FocusDirection& direction) const
{
// Since float equality is tricky (arithmetic is non-associative, commutative),
// test if the two numbers are within an epsilon distance of each other.
auto floatEqual = [](float left, float right) {
return abs(left - right) < 1e-4F;
};
// When checking containment in a range, the range is half-closed, i.e. [x, x+w).
// If the direction is left test that the left side of the first element is
// next to the right side of the second element, and that the top left
// corner of the first element is within the second element's height
if (direction == FocusDirection::Left)
{
auto sharesBorders = floatEqual(firstOffset.x, secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
return sharesBorders && withinHeight;
}
// If the direction is right test that the right side of the first element is
// next to the left side of the second element, and that the top left
// corner of the first element is within the second element's height
else if (direction == FocusDirection::Right)
{
auto sharesBorders = floatEqual(firstOffset.x + gsl::narrow_cast<float>(first->GetRootElement().ActualWidth()), secondOffset.x);
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
return sharesBorders && withinHeight;
}
// If the direction is up test that the top side of the first element is
// next to the bottom side of the second element, and that the top left
// corner of the first element is within the second element's width
else if (direction == FocusDirection::Up)
{
auto sharesBorders = floatEqual(firstOffset.y, secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
return sharesBorders && withinWidth;
}
// If the direction is down test that the bottom side of the first element is
// next to the top side of the second element, and that the top left
// corner of the first element is within the second element's width
else if (direction == FocusDirection::Down)
{
auto sharesBorders = floatEqual(firstOffset.y + gsl::narrow_cast<float>(first->GetRootElement().ActualHeight()), secondOffset.y);
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
return sharesBorders && withinWidth;
}
return false;
}
// Method Description:
// - Given the focused pane, and its relative position in the tree, attempt to
// find its visual neighbor within the current pane's tree.
// The neighbor, if it exists, will be a leaf pane.
// Arguments:
// - direction: The direction to search in from the focused pane.
// - focus: the focused pane
// - focusIsSecondSide: If the focused pane is on the "second" side (down/right of split)
// relative to the branch being searched
// - offset: the offset of the current pane
// Return Value:
// - A tuple of Panes, the first being the focused pane if found, and the second
// being the adjacent pane if it exists, and a bool that represents if the move
// goes out of bounds.
Pane::FocusNeighborSearch Pane::_FindNeighborForPane(const FocusDirection& direction,
FocusNeighborSearch searchResult,
const bool focusIsSecondSide,
const Pane::PanePoint offset)
{
// Test if the move will go out of boundaries. E.g. if the focus is already
// on the second child of some pane and it attempts to move right, there
// can't possibly be a neighbor to be found in the first child.
if ((focusIsSecondSide && (direction == FocusDirection::Right || direction == FocusDirection::Down)) ||
(!focusIsSecondSide && (direction == FocusDirection::Left || direction == FocusDirection::Up)))
{
return searchResult;
}
// If we are a leaf node test if we adjacent to the focus node
if (_IsLeaf())
{
if (_IsAdjacent(searchResult.focus, searchResult.focusOffset, shared_from_this(), offset, direction))
{
searchResult.neighbor = shared_from_this();
}
return searchResult;
}
auto firstOffset = offset;
auto secondOffset = offset;
// The second child has an offset depending on the split
if (_splitState == SplitState::Horizontal)
{
secondOffset.y += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
}
else
{
secondOffset.x += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
}
auto focusNeighborSearch = _firstChild->_FindNeighborForPane(direction, searchResult, focusIsSecondSide, firstOffset);
if (focusNeighborSearch.neighbor)
{
return focusNeighborSearch;
}
return _secondChild->_FindNeighborForPane(direction, searchResult, focusIsSecondSide, secondOffset);
}
// Method Description:
// - Searches the tree to find the currently focused pane, and if it exists, the
// visually adjacent pane by direction.
// Arguments:
// - direction: The direction to search in from the focused pane.
// - offset: The offset, with the top-left corner being (0,0), that the current pane is relative to the root.
// Return Value:
// - The (partial) search result. If the search was successful, the focus and its neighbor will be returned.
// Otherwise, the neighbor will be null and the focus will be null/non-null if it was found.
Pane::FocusNeighborSearch Pane::_FindFocusAndNeighbor(const FocusDirection& direction, const Pane::PanePoint offset)
{
// If we are the currently focused pane, return ourselves
if (_IsLeaf())
{
return { _lastActive ? shared_from_this() : nullptr, nullptr, offset };
}
// Search the first child, which has no offset from the parent pane
auto firstOffset = offset;
auto secondOffset = offset;
// The second child has an offset depending on the split
if (_splitState == SplitState::Horizontal)
{
secondOffset.y += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
}
else
{
secondOffset.x += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
}
auto focusNeighborSearch = _firstChild->_FindFocusAndNeighbor(direction, firstOffset);
// If we have both the focus element and its neighbor, we are done
if (focusNeighborSearch.focus && focusNeighborSearch.neighbor)
{
return focusNeighborSearch;
}
// if we only found the focus, then we search the second branch for the
// neighbor.
if (focusNeighborSearch.focus)
{
// If we can possibly have both sides of a direction, check if the sibling has the neighbor
if (DirectionMatchesSplit(direction, _splitState))
{
return _secondChild->_FindNeighborForPane(direction, focusNeighborSearch, false, secondOffset);
}
return focusNeighborSearch;
}
// If we didn't find the focus at all, we need to search the second branch
// for the focus (and possibly its neighbor).
focusNeighborSearch = _secondChild->_FindFocusAndNeighbor(direction, secondOffset);
// We found both so we are done.
if (focusNeighborSearch.focus && focusNeighborSearch.neighbor)
{
return focusNeighborSearch;
}
// We only found the focus, which means that its neighbor might be in the
// first branch.
if (focusNeighborSearch.focus)
{
// If we can possibly have both sides of a direction, check if the sibling has the neighbor
if (DirectionMatchesSplit(direction, _splitState))
{
return _firstChild->_FindNeighborForPane(direction, focusNeighborSearch, true, firstOffset);
}
return focusNeighborSearch;
}
return { nullptr, nullptr, offset };
}
// Method Description:
// - Attempts to swap places of the focused pane with one of our children. This
// will swap with the visually adjacent leaf pane if one exists in the
// direction requested, maintaining the existing tree structure.
// This breaks down into a few possible cases
// - If the move direction would encounter the edge of the pane, no move occurs
// - If the focused pane has a single neighbor according to the direction,
// then it will swap with it.
// - If the focused pane has multiple neighbors, it will swap with the
// first-most leaf of the neighboring panes.
// Arguments:
// - direction: The direction to move the focused pane in.
// Return Value:
// - true if we or a child handled this pane move request.
bool Pane::MovePane(const FocusDirection& direction)
{
// If we're a leaf, do nothing. We can't possibly swap anything.
if (_IsLeaf())
{
return false;
}
// If we get a request to move to the previous pane return false because
// that needs to be handled at the tab level.
if (direction == FocusDirection::Previous)
{
return false;
}
// If the move direction does not match the split direction, the focused pane
// and its neighbor must necessarily be contained within the same child.
if (!DirectionMatchesSplit(direction, _splitState))
{
return _firstChild->MovePane(direction) || _secondChild->MovePane(direction);
}
// Since the direction is the same as our split, it is possible that we must
// swap a pane from one child to the other child.
// We now must keep track of state while we recurse.
auto focusNeighborPair = _FindFocusAndNeighbor(direction, { 0, 0 });
// Once we have found the focused pane and its neighbor, wherever they may
// be, we can swap them.
if (focusNeighborPair.focus && focusNeighborPair.neighbor)
{
auto swapped = SwapPanes(focusNeighborPair.focus, focusNeighborPair.neighbor);
focusNeighborPair.focus->_FocusFirstChild();
return swapped;
}
return false;
@@ -911,7 +1243,6 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
auto removedChild = closeFirst ? _firstChild : _secondChild;
auto remainingChild = closeFirst ? _secondChild : _firstChild;
const bool splitWidth = _splitState == SplitState::Vertical;
const auto totalSize = splitWidth ? _root.ActualWidth() : _root.ActualHeight();
Size removedOriginalSize{
::base::saturated_cast<float>(removedChild->_root.ActualWidth()),
@@ -1626,6 +1957,36 @@ bool Pane::FocusPane(const uint32_t id)
return false;
}
// Method Description:
// - Recursive function that finds a pane with the given ID
// Arguments:
// - The ID of the pane we want to find
// Return Value:
// - A pointer to the pane with the given ID, if found.
std::shared_ptr<Pane> Pane::FindPane(const uint32_t id)
{
if (_IsLeaf())
{
if (id == _id)
{
return shared_from_this();
}
}
else
{
if (auto pane = _firstChild->FindPane(id))
{
return pane;
}
if (auto pane = _secondChild->FindPane(id))
{
return pane;
}
}
return nullptr;
}
// Method Description:
// - Gets the size in pixels of each of our children, given the full size they
// should fill. Since these children own their own separators (borders), this

View File

@@ -22,6 +22,12 @@
#include "../../cascadia/inc/cppwinrt_utils.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
enum class Borders : int
{
None = 0x0,
@@ -56,6 +62,8 @@ public:
void Relayout();
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool MovePane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
@@ -79,6 +87,7 @@ public:
std::optional<uint32_t> Id() noexcept;
void Id(uint32_t id) noexcept;
bool FocusPane(const uint32_t id);
std::shared_ptr<Pane> FindPane(const uint32_t id);
bool ContainsReadOnly() const;
@@ -88,6 +97,8 @@ public:
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
private:
struct PanePoint;
struct FocusNeighborSearch;
struct SnapSizeResult;
struct SnapChildrenSizeResult;
struct LayoutSizeNode;
@@ -137,7 +148,15 @@ private:
void _UpdateBorders();
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool _NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
std::shared_ptr<Pane> _FindParentOfPane(const std::shared_ptr<Pane> pane);
bool _IsAdjacent(const std::shared_ptr<Pane> first, const PanePoint firstOffset, const std::shared_ptr<Pane> second, const PanePoint secondOffset, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) const;
FocusNeighborSearch _FindNeighborForPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
FocusNeighborSearch searchResult,
const bool focusIsSecondSide,
const PanePoint offset);
FocusNeighborSearch _FindFocusAndNeighbor(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
const PanePoint offset);
void _CloseChild(const bool closeFirst);
winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst);
@@ -200,6 +219,19 @@ private:
static void _SetupResources();
struct PanePoint
{
float x;
float y;
};
struct FocusNeighborSearch
{
std::shared_ptr<Pane> focus;
std::shared_ptr<Pane> neighbor;
PanePoint focusOffset;
};
struct SnapSizeResult
{
float lower;
@@ -236,4 +268,6 @@ private:
private:
void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode);
};
friend class ::TerminalAppLocalTests::TabTests;
};

View File

@@ -359,6 +359,16 @@
<data name="CmdMoveFocusDirectionArgDesc" xml:space="preserve">
<value>The direction to move focus in</value>
</data>
<data name="CmdMovePaneDesc" xml:space="preserve">
<value>Swap the focused pane with the adjacent pane in the specified direction</value>
</data>
<data name="CmdMPDesc" xml:space="preserve">
<value>An alias for the "move-pane" subcommand.</value>
<comment>{Locked="\"move-pane\""}</comment>
</data>
<data name="CmdMovePaneDirectionArgDesc" xml:space="preserve">
<value>The direction to move the focused pane in</value>
</data>
<data name="CmdFocusDesc" xml:space="preserve">
<value>Launch the window in focus mode</value>
</data>
@@ -649,4 +659,7 @@
<value>Open in Windows Terminal</value>
<comment>{Locked="Windows"} This is a menu item that will be displayed in the Windows File Explorer that launches the non-preview version of Windows Terminal</comment>
</data>
<data name="DropPathTabRun.Text" xml:space="preserve">
<value>Open a new tab in given starting directory</value>
</data>
</root>

View File

@@ -105,6 +105,14 @@ namespace winrt::TerminalApp::implementation
// Create a connection based on the values in our settings object if we weren't given one.
auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profileGuid, settings.DefaultSettings());
// If we had an `existingConnection`, then this is an inbound handoff from somewhere else.
// We need to tell it about our size information so it can match the dimensions of what
// we are about to present.
if (existingConnection)
{
connection.Resize(settings.DefaultSettings().InitialRows(), settings.DefaultSettings().InitialCols());
}
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
if (_settings.GlobalSettings().DebugFeaturesEnabled())
{
@@ -497,24 +505,25 @@ namespace winrt::TerminalApp::implementation
// TerminalPage::_OnTabSelectionChanged
// Return Value:
// true iff we were able to select that tab index, false otherwise
bool TerminalPage::_SelectTab(const uint32_t tabIndex)
bool TerminalPage::_SelectTab(uint32_t tabIndex)
{
if (tabIndex >= 0 && tabIndex < _tabs.Size())
{
auto tab{ _tabs.GetAt(tabIndex) };
if (_startupState == StartupState::InStartup)
{
_tabView.SelectedItem(tab.TabViewItem());
_UpdatedSelectedTab(tab);
}
else
{
_SetFocusedTab(tab);
}
// GH#9369 - if the argument is out of range, then clamp to the number
// of available tabs. Previously, we'd just silently do nothing if the
// value was greater than the number of tabs.
tabIndex = std::clamp(tabIndex, 0u, _tabs.Size() - 1);
return true;
auto tab{ _tabs.GetAt(tabIndex) };
if (_startupState == StartupState::InStartup)
{
_tabView.SelectedItem(tab.TabViewItem());
_UpdatedSelectedTab(tab);
}
return false;
else
{
_SetFocusedTab(tab);
}
return true;
}
// Method Description:

View File

@@ -5,9 +5,19 @@
#include "TabRowControl.h"
#include "TabRowControl.g.cpp"
#include <LibraryResources.h>
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt;
using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Windows::UI::Text;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
namespace WUX = Windows::UI::Xaml;
}
namespace winrt::TerminalApp::implementation
{
@@ -23,4 +33,42 @@ namespace winrt::TerminalApp::implementation
void TabRowControl::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&)
{
}
// Method Description:
// - Bound in Drag&Drop of the Xaml editor to the [+] button.
// Arguments:
// <unused>
void TabRowControl::OnNewTabButtonDrop(IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs const&)
{
}
// Method Description:
// - Bound in Drag-over of the Xaml editor to the [+] button.
// Allows drop of 'StorageItems' which will be used as StartingDirectory
// Arguments:
// - <unused>
// - e: DragEventArgs which hold the items
void TabRowControl::OnNewTabButtonDragOver(IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs const& e)
{
// We can only handle drag/dropping StorageItems (files).
// If the format on the clipboard is anything else, returning
// early here will prevent the drag/drop from doing anything.
if (!e.DataView().Contains(StandardDataFormats::StorageItems()))
{
return;
}
// Make sure to set the AcceptedOperation, so that we can later receive the path in the Drop event
e.AcceptedOperation(DataPackageOperation::Copy);
// Sets custom UI text
e.DragUIOverride().Caption(RS_(L"DropPathTabRun/Text"));
// Sets if the caption is visible
e.DragUIOverride().IsCaptionVisible(true);
// Sets if the dragged content is visible
e.DragUIOverride().IsContentVisible(false);
// Sets if the glyph is visible
e.DragUIOverride().IsGlyphVisible(false);
}
}

View File

@@ -14,6 +14,8 @@ namespace winrt::TerminalApp::implementation
TabRowControl();
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
void OnNewTabButtonDrop(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
void OnNewTabButtonDragOver(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
};
}

View File

@@ -25,11 +25,14 @@
x:Uid="NewTabSplitButton"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
AllowDrop="True"
AutomationProperties.AccessibilityView="Control"
BorderThickness="0"
Click="OnNewTabButtonClick"
Content="&#xE710;"
CornerRadius="{Binding Source={ThemeResource OverlayCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}"
DragOver="OnNewTabButtonDragOver"
Drop="OnNewTabButtonDrop"
FontFamily="Segoe MDL2 Assets"
FontSize="12"
FontWeight="SemiLight"

View File

@@ -6,6 +6,7 @@
#include "Utils.h"
#include "../../types/inc/utils.hpp"
#include <filesystem>
#include <LibraryResources.h>
#include "TerminalPage.g.cpp"
@@ -172,40 +173,13 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
// if alt is pressed, open a pane
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
// Check for DebugTap
bool debugTap = page->_settings.GlobalSettings().DebugFeaturesEnabled() &&
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (altPressed && !debugTap)
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
nullptr);
}
else if (shiftPressed && !debugTap)
{
page->_OpenNewWindow(false, NewTerminalArgs());
}
else
{
page->_OpenNewTab(nullptr);
}
page->_OpenNewTerminal(NewTerminalArgs());
}
});
_newTabButton.Drop([weakThis{ get_weak() }](Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs e) {
if (auto page{ weakThis.get() })
{
page->NewTerminalByDrop(e);
}
});
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
@@ -262,6 +236,44 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG();
}
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
{
Windows::Foundation::Collections::IVectorView<Windows::Storage::IStorageItem> items;
try
{
items = co_await e.DataView().GetStorageItemsAsync();
}
CATCH_LOG();
if (items.Size() == 1)
{
std::filesystem::path path(items.GetAt(0).Path().c_str());
if (!std::filesystem::is_directory(path))
{
path = path.parent_path();
}
std::wstring pathText = path.wstring();
// Handle edge case of "C:\\", seems like the "StartingDirectory" doesn't like path which ends with '\'
if (pathText.back() == L'\\')
{
pathText.erase(std::prev(pathText.end()));
}
NewTerminalArgs args;
args.StartingDirectory(winrt::hstring{ pathText });
this->_OpenNewTerminal(args);
TraceLoggingWrite(
g_hTerminalAppProvider,
"NewTabByDragDrop",
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
}
}
// Method Description:
// - This method is called once command palette action was chosen for dispatching
// We'll use this event to dispatch this command.
@@ -622,43 +634,7 @@ namespace winrt::TerminalApp::implementation
if (auto page{ weakThis.get() })
{
NewTerminalArgs newTerminalArgs{ profileIndex };
// if alt is pressed, open a pane
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
// Check for DebugTap
bool debugTap = page->_settings.GlobalSettings().DebugFeaturesEnabled() &&
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (altPressed && !debugTap)
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
newTerminalArgs);
}
else if (shiftPressed && !debugTap)
{
// Manually fill in the evaluated profile.
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(page->_settings.GetProfileForArgs(newTerminalArgs)));
page->_OpenNewWindow(false, newTerminalArgs);
}
else
{
page->_OpenNewTab(newTerminalArgs);
}
page->_OpenNewTerminal(newTerminalArgs);
}
});
newTabFlyout.Items().Append(profileMenuItem);
@@ -752,6 +728,49 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Flyout().ShowAt(_newTabButton);
}
void TerminalPage::_OpenNewTerminal(const NewTerminalArgs newTerminalArgs)
{
// if alt is pressed, open a pane
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
// Check for DebugTap
bool debugTap = this->_settings.GlobalSettings().DebugFeaturesEnabled() &&
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (altPressed && !debugTap)
{
this->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
newTerminalArgs);
}
else if (shiftPressed && !debugTap)
{
// Manually fill in the evaluated profile.
if (newTerminalArgs.ProfileIndex() != nullptr)
{
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(this->_settings.GetProfileForArgs(newTerminalArgs)));
}
this->_OpenNewWindow(false, newTerminalArgs);
}
else
{
this->_OpenNewTab(newTerminalArgs);
}
}
winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page)
{
co_await winrt::resume_foreground(page->_tabView.Dispatcher());
@@ -785,13 +804,14 @@ namespace winrt::TerminalApp::implementation
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
connection = TerminalConnection::ConptyConnection(azBridgePath.wstring(),
L".",
L"Azure",
nullptr,
settings.InitialRows(),
settings.InitialCols(),
winrt::guid());
connection = TerminalConnection::ConptyConnection();
connection.Initialize(TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
L".",
L"Azure",
nullptr,
::base::saturated_cast<uint32_t>(settings.InitialRows()),
::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid()));
}
else
@@ -821,14 +841,14 @@ namespace winrt::TerminalApp::implementation
std::filesystem::path cwd{ cwdString };
cwd /= settings.StartingDirectory().c_str();
auto conhostConn = TerminalConnection::ConptyConnection(
settings.Commandline(),
winrt::hstring{ cwd.c_str() },
settings.StartingTitle(),
envMap.GetView(),
settings.InitialRows(),
settings.InitialCols(),
winrt::guid());
auto conhostConn = TerminalConnection::ConptyConnection();
conhostConn.Initialize(TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
winrt::hstring{ cwd.wstring() },
settings.StartingTitle(),
envMap.GetView(),
::base::saturated_cast<uint32_t>(settings.InitialRows()),
::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid()));
sessionGuid = conhostConn.Guid();
connection = conhostConn;
@@ -914,12 +934,14 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
const auto key = e.OriginalKey();
const auto scanCode = e.KeyStatus().ScanCode;
const auto coreWindow = CoreWindow::GetForCurrentThread();
const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, false, static_cast<int32_t>(key), static_cast<int32_t>(scanCode) };
if (const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) })
{
if (CommandPalette().Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
@@ -1111,6 +1133,22 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Attempt to swap the positions of the focused pane with another pane.
// See Pane::MovePane for details.
// Arguments:
// - direction: The direction to move the focused pane in.
// Return Value:
// - <none>
void TerminalPage::_MovePane(const FocusDirection& direction)
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
_UnZoomIfNeeded();
terminalTab->MovePane(direction);
}
}
TermControl TerminalPage::_GetActiveControl()
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
@@ -2020,11 +2058,11 @@ namespace winrt::TerminalApp::implementation
// - Gets the taskbar state value from the last active control
// Return Value:
// - The taskbar state of the last active control
size_t TerminalPage::GetLastActiveControlTaskbarState()
uint64_t TerminalPage::GetLastActiveControlTaskbarState()
{
if (auto control{ _GetActiveControl() })
{
return gsl::narrow_cast<size_t>(control.TaskbarState());
return control.TaskbarState();
}
return {};
}
@@ -2033,11 +2071,11 @@ namespace winrt::TerminalApp::implementation
// - Gets the taskbar progress value from the last active control
// Return Value:
// - The taskbar progress of the last active control
size_t TerminalPage::GetLastActiveControlTaskbarProgress()
uint64_t TerminalPage::GetLastActiveControlTaskbarProgress()
{
if (auto control{ _GetActiveControl() })
{
return gsl::narrow_cast<size_t>(control.TaskbarProgress());
return control.TaskbarProgress();
}
return {};
}

View File

@@ -58,6 +58,8 @@ namespace winrt::TerminalApp::implementation
void Create();
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
hstring Title();
void TitlebarClicked();
@@ -83,8 +85,8 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter);
size_t GetLastActiveControlTaskbarState();
size_t GetLastActiveControlTaskbarProgress();
uint64_t GetLastActiveControlTaskbarState();
uint64_t GetLastActiveControlTaskbarProgress();
void ShowKeyboardServiceWarning();
winrt::hstring KeyboardServiceDisabledText();
@@ -192,6 +194,8 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
void _OpenNewTerminal(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
bool _displayingCloseDialog{ false };
void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _CommandPaletteButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
@@ -229,8 +233,9 @@ namespace winrt::TerminalApp::implementation
void _ResizeTabContent(const winrt::Windows::Foundation::Size& newSize);
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
bool _SelectTab(const uint32_t tabIndex);
bool _SelectTab(uint32_t tabIndex);
void _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
void _MovePane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;

View File

@@ -486,6 +486,10 @@ namespace winrt::TerminalApp::implementation
{
if (direction == FocusDirection::Previous)
{
if (_mruPanes.size() < 2)
{
return;
}
// To get to the previous pane, get the id of the previous pane and focus to that
_rootPane->FocusPane(_mruPanes.at(1));
}
@@ -497,6 +501,35 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Attempts to swap the location of the focused pane with another pane
// according to direction. When there are multiple adjacent panes it will
// select the first one (top-left-most).
// Arguments:
// - direction: The direction to move the pane in.
// Return Value:
// - <none>
void TerminalTab::MovePane(const FocusDirection& direction)
{
if (direction == FocusDirection::Previous)
{
if (_mruPanes.size() < 2)
{
return;
}
if (auto lastPane = _rootPane->FindPane(_mruPanes.at(1)))
{
_rootPane->SwapPanes(_activePane, lastPane);
}
}
else
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
_rootPane->MovePane(direction);
}
}
bool TerminalTab::FocusPane(const uint32_t id)
{
return _rootPane->FocusPane(id);

View File

@@ -53,6 +53,7 @@ namespace winrt::TerminalApp::implementation
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
void MovePane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool FocusPane(const uint32_t id);
void UpdateSettings(const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const GUID& profile);

View File

@@ -96,7 +96,11 @@ int wmain(int /*argc*/, wchar_t** /*argv*/)
const auto size = GetConsoleScreenSize(conOut);
AzureConnection azureConn{ gsl::narrow_cast<uint32_t>(size.Y), gsl::narrow_cast<uint32_t>(size.X) };
AzureConnection azureConn{};
winrt::Windows::Foundation::Collections::ValueSet vs{};
vs.Insert(L"initialRows", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.Y)));
vs.Insert(L"initialCols", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.X)));
azureConn.Initialize(vs);
const auto state = RunConnectionToCompletion(azureConn, conOut, conIn);

View File

@@ -33,6 +33,7 @@ Abstract:
#include <wil/cppwinrt.h>
#include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <wil/resource.h>

View File

@@ -71,11 +71,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return (AzureClientID != L"0");
}
AzureConnection::AzureConnection(const uint32_t initialRows, const uint32_t initialCols) :
_initialRows{ initialRows },
_initialCols{ initialCols },
_expiry{}
void AzureConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
{
if (settings)
{
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
}
}
// Method description:

View File

@@ -21,7 +21,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
static winrt::guid ConnectionType() noexcept;
static bool IsAzureConnectionAvailable() noexcept;
AzureConnection(const uint32_t rows, const uint32_t cols);
AzureConnection() = default;
void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
void Start();
void WriteInput(hstring const& data);

View File

@@ -10,7 +10,7 @@ namespace Microsoft.Terminal.TerminalConnection
static Guid ConnectionType { get; };
static Boolean IsAzureConnectionAvailable();
AzureConnection(UInt32 rows, UInt32 columns);
AzureConnection();
};
}

View File

@@ -0,0 +1,66 @@
#include "pch.h"
#include "ConnectionInformation.h"
#include "ConnectionInformation.g.cpp"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
ConnectionInformation::ConnectionInformation(hstring const& className,
const Windows::Foundation::Collections::ValueSet& settings) :
_ClassName{ className },
_Settings{ settings }
{
}
// Function Description:
// - Create an instance of the connection specified in the
// ConnectionInformation, and Initialize it.
// - This static method allows the content process to create a connection
// from information that lives in the window process.
// Arguments:
// - info: A ConnectionInformation object that possibly lives out-of-proc,
// containing the name of the WinRT class we should activate for this
// connection, and a bag of setting to use to initialize that object.
// Return Value:
// - <none>
TerminalConnection::ITerminalConnection ConnectionInformation::CreateConnection(TerminalConnection::ConnectionInformation info)
try
{
Windows::Foundation::IInspectable inspectable{};
const auto name = static_cast<HSTRING>(winrt::get_abi(info.ClassName()));
const auto pointer = winrt::put_abi(inspectable);
#pragma warning(push)
#pragma warning(disable : 26490)
// C++/WinRT just loves it's void**, nothing we can do here _except_ reinterpret_cast
::IInspectable** raw = reinterpret_cast<::IInspectable**>(pointer);
#pragma warning(pop)
// RoActivateInstance() will try to create an instance of the object,
// who's fully qualified name is the string in Name().
//
// The class has to be activatable. For the Terminal, this is easy
// enough - we're not hosting anything that's not already in our
// manifest, or living as a .dll & .winmd SxS.
//
// When we get to extensions (GH#4000), we may want to revisit.
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
{
return nullptr;
}
// Now that thing we made, make sure it's actually a ITerminalConnection
if (const auto connection{ inspectable.try_as<TerminalConnection::ITerminalConnection>() })
{
// Initialize it, and return it.
connection.Initialize(info.Settings());
return connection;
}
return nullptr;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return nullptr;
}
}

View File

@@ -0,0 +1,43 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Class Name:
- ConnectionInformation.h
Abstract:
- This is a helper object for storing both the name of a type of connection, and
a bag of settings to use to initialize that connection.
- This helper is used primarily in cross-proc scenarios, to allow the window
process to tell the content process the name of the connection type it wants
created, and how to set that connection up. This is done so the connection can
live entirely in the content process, without having to go through the window
process at all.
--*/
#pragma once
#include "../inc/cppwinrt_utils.h"
#include "ConnectionInformation.g.h"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConnectionInformation : ConnectionInformationT<ConnectionInformation>
{
ConnectionInformation(hstring const& className,
const Windows::Foundation::Collections::ValueSet& settings);
static TerminalConnection::ITerminalConnection CreateConnection(TerminalConnection::ConnectionInformation info);
winrt::hstring ClassName() const { return _ClassName; }
void ClassName(const winrt::hstring& value) { _ClassName = value; }
WINRT_PROPERTY(Windows::Foundation::Collections::ValueSet, Settings);
private:
winrt::hstring _ClassName{};
};
}
namespace winrt::Microsoft::Terminal::TerminalConnection::factory_implementation
{
BASIC_FACTORY(ConnectionInformation);
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ITerminalConnection.idl";
namespace Microsoft.Terminal.TerminalConnection
{
[default_interface] runtimeclass ConnectionInformation
{
ConnectionInformation(String className, Windows.Foundation.Collections.ValueSet settings);
String ClassName { get; };
Windows.Foundation.Collections.ValueSet Settings { get; };
static ITerminalConnection CreateConnection(ConnectionInformation info);
}
}

View File

@@ -116,17 +116,23 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// add additional WT env vars like WT_SETTINGS, WT_DEFAULTS and WT_PROFILE_ID
for (auto item : _environment)
{
auto key = item.Key();
auto value = item.Value();
// avoid clobbering WSLENV
if (std::wstring_view{ key } == L"WSLENV")
try
{
auto current = environment[L"WSLENV"];
value = current + L":" + value;
}
auto key = item.Key();
// This will throw if the value isn't a string. If that
// happens, then just skip this entry.
auto value = winrt::unbox_value<hstring>(item.Value());
environment.insert_or_assign(key.c_str(), value.c_str());
// avoid clobbering WSLENV
if (std::wstring_view{ key } == L"WSLENV")
{
auto current = environment[L"WSLENV"];
value = current + L":" + value;
}
environment.insert_or_assign(key.c_str(), value.c_str());
}
CATCH_LOG();
}
}
@@ -219,24 +225,54 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_piClient.hProcess = hClientProcess;
}
ConptyConnection::ConptyConnection(const hstring& commandline,
const hstring& startingDirectory,
const hstring& startingTitle,
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
const uint32_t initialRows,
const uint32_t initialCols,
const guid& initialGuid) :
_initialRows{ initialRows },
_initialCols{ initialCols },
_commandline{ commandline },
_startingDirectory{ startingDirectory },
_startingTitle{ startingTitle },
_environment{ environment },
_guid{ initialGuid },
_u8State{},
_u16Str{},
_buffer{}
// Function Description:
// - Helper function for constructing a ValueSet that we can use to get our settings from.
Windows::Foundation::Collections::ValueSet ConptyConnection::CreateSettings(const winrt::hstring& cmdline,
const winrt::hstring& startingDirectory,
const winrt::hstring& startingTitle,
Windows::Foundation::Collections::IMapView<hstring, hstring> const& environment,
uint32_t rows,
uint32_t columns,
winrt::guid const& guid)
{
Windows::Foundation::Collections::ValueSet vs{};
vs.Insert(L"commandline", Windows::Foundation::PropertyValue::CreateString(cmdline));
vs.Insert(L"startingDirectory", Windows::Foundation::PropertyValue::CreateString(startingDirectory));
vs.Insert(L"startingTitle", Windows::Foundation::PropertyValue::CreateString(startingTitle));
vs.Insert(L"initialRows", Windows::Foundation::PropertyValue::CreateUInt32(rows));
vs.Insert(L"initialCols", Windows::Foundation::PropertyValue::CreateUInt32(columns));
vs.Insert(L"guid", Windows::Foundation::PropertyValue::CreateGuid(guid));
if (environment)
{
Windows::Foundation::Collections::ValueSet env{};
for (const auto& [k, v] : environment)
{
env.Insert(k, Windows::Foundation::PropertyValue::CreateString(v));
}
vs.Insert(L"environment", env);
}
return vs;
}
void ConptyConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
{
if (settings)
{
// For the record, the following won't crash:
// auto bad = unbox_value_or<hstring>(settings.TryLookup(L"foo").try_as<IPropertyValue>(), nullptr);
// It'll just return null
_commandline = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"commandline").try_as<Windows::Foundation::IPropertyValue>(), _commandline);
_startingDirectory = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingDirectory").try_as<Windows::Foundation::IPropertyValue>(), _startingDirectory);
_startingTitle = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingTitle").try_as<Windows::Foundation::IPropertyValue>(), _startingTitle);
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
_guid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"guid").try_as<Windows::Foundation::IPropertyValue>(), _guid);
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
}
if (_guid == guid{})
{
_guid = Utils::CreateGuid();
@@ -253,12 +289,21 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
_transitionToState(ConnectionState::Connecting);
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
// If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received
// handoff from an already-started PTY process.
if (!_inPipe)
{
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE, &_inPipe, &_outPipe, &_hPC));
THROW_IF_FAILED(_LaunchAttachedClient());
}
// But if it was an inbound handoff... attempt to synchronize the size of it with what our connection
// window is expecting it to be on the first layout.
else
{
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), dimensions));
}
_startTime = std::chrono::high_resolution_clock::now();
@@ -387,11 +432,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Resize(uint32_t rows, uint32_t columns)
{
if (!_hPC)
// If we haven't started connecting at all, it's still fair to update
// the initial rows and columns before we set things up.
if (!_isStateAtOrBeyond(ConnectionState::Connecting))
{
_initialRows = rows;
_initialCols = columns;
}
// Otherwise, we can really only dispatch a resize if we're already connected.
else if (_isConnected())
{
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) }));

View File

@@ -26,14 +26,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const HANDLE hServerProcess,
const HANDLE hClientProcess);
ConptyConnection(
const hstring& cmdline,
const hstring& startingDirectory,
const hstring& startingTitle,
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
const uint32_t rows,
const uint32_t cols,
const guid& guid);
ConptyConnection() noexcept = default;
void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
static winrt::fire_and_forget final_release(std::unique_ptr<ConptyConnection> connection);
void Start();
@@ -49,6 +44,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
static winrt::event_token NewConnection(NewConnectionHandler const& handler);
static void NewConnection(winrt::event_token const& token);
static Windows::Foundation::Collections::ValueSet CreateSettings(const winrt::hstring& cmdline,
const winrt::hstring& startingDirectory,
const winrt::hstring& startingTitle,
Windows::Foundation::Collections::IMapView<hstring, hstring> const& environment,
uint32_t rows,
uint32_t columns,
winrt::guid const& guid);
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
private:
@@ -60,10 +63,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
uint32_t _initialRows{};
uint32_t _initialCols{};
hstring _commandline;
hstring _startingDirectory;
hstring _startingTitle;
Windows::Foundation::Collections::IMapView<hstring, hstring> _environment;
hstring _commandline{};
hstring _startingDirectory{};
hstring _startingTitle{};
Windows::Foundation::Collections::ValueSet _environment{ nullptr };
guid _guid{}; // A unique session identifier for connected client
hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch).
@@ -77,9 +80,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
wil::unique_static_pseudoconsole_handle _hPC;
wil::unique_threadpool_wait _clientExitWait;
til::u8state _u8State;
std::wstring _u16Str;
std::array<char, 4096> _buffer;
til::u8state _u8State{};
std::wstring _u16Str{};
std::array<char, 4096> _buffer{};
DWORD _OutputThread();
};

View File

@@ -7,11 +7,19 @@ namespace Microsoft.Terminal.TerminalConnection
{
[default_interface] runtimeclass ConptyConnection : ITerminalConnection
{
ConptyConnection(String cmdline, String startingDirectory, String startingTitle, IMapView<String, String> environment, UInt32 rows, UInt32 columns, Guid guid);
ConptyConnection();
Guid Guid { get; };
static event NewConnectionHandler NewConnection;
static void StartInboundListener();
static void StopInboundListener();
static Windows.Foundation.Collections.ValueSet CreateSettings(String cmdline,
String startingDirectory,
String startingTitle,
IMapView<String, String> environment,
UInt32 rows,
UInt32 columns,
Guid guid);
};
}

View File

@@ -18,6 +18,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void Resize(uint32_t rows, uint32_t columns) noexcept;
void Close() noexcept;
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) const noexcept {};
ConnectionState State() const noexcept { return ConnectionState::Connected; }
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);

View File

@@ -17,6 +17,8 @@ namespace Microsoft.Terminal.TerminalConnection
interface ITerminalConnection
{
void Initialize(Windows.Foundation.Collections.ValueSet settings);
void Start();
void WriteInput(String data);
void Resize(UInt32 rows, UInt32 columns);

View File

@@ -13,6 +13,9 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemGroup>
<ClInclude Include="AzureClientID.h" />
<ClInclude Include="ConnectionInformation.h">
<DependentUpon>ConnectionInformation.idl</DependentUpon>
</ClInclude>
<ClInclude Include="AzureConnection.h">
<DependentUpon>AzureConnection.idl</DependentUpon>
</ClInclude>
@@ -28,6 +31,9 @@
<ItemGroup>
<ClCompile Include="CTerminalHandoff.cpp" />
<ClCompile Include="init.cpp" />
<ClCompile Include="ConnectionInformation.cpp">
<DependentUpon>ConnectionInformation.idl</DependentUpon>
</ClCompile>
<ClCompile Include="AzureConnection.cpp">
<DependentUpon>AzureConnection.idl</DependentUpon>
</ClCompile>
@@ -43,6 +49,7 @@
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="ConnectionInformation.idl" />
<Midl Include="ITerminalConnection.idl" />
<Midl Include="ConptyConnection.idl" />
<Midl Include="EchoConnection.idl" />

View File

@@ -155,6 +155,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
_renderEngine = std::move(dxEngine);
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
@@ -168,12 +169,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Then, using the font, get the number of characters that can fit.
// Resize our terminal connection to match that size, and initialize the terminal with that size.
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
LOG_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
LOG_IF_FAILED(_renderEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
// Update DxEngine's SelectionBackground
dxEngine->SetSelectionBackground(til::color{ _settings.SelectionBackground() });
_renderEngine->SetSelectionBackground(til::color{ _settings.SelectionBackground() });
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
const auto width = vp.Width();
const auto height = vp.Height();
_connection.Resize(height, width);
@@ -188,27 +189,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// after Enable, then it'll be possible to paint the frame once
// _before_ the warning handler is set up, and then warnings from
// the first paint will be ignored!
dxEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
_renderEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
// Tell the DX Engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
dxEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
_renderEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
dxEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect());
dxEngine->SetPixelShaderPath(_settings.PixelShaderPath());
dxEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
dxEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_renderEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect());
_renderEngine->SetPixelShaderPath(_settings.PixelShaderPath());
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_updateAntiAliasingMode(dxEngine.get());
_updateAntiAliasingMode(_renderEngine.get());
// GH#5098: Inform the engine of the opacity of the default text background.
if (_settings.UseAcrylic())
{
dxEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
}
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
THROW_IF_FAILED(_renderEngine->Enable());
_initializedTerminal = true;
} // scope for TerminalLock
@@ -426,7 +426,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - Updates last hovered cell, renders / removes rendering of hyper-link if required
// Arguments:
// - terminalPosition: The terminal position of the pointer
void ControlCore::UpdateHoveredCell(const std::optional<til::point>& terminalPosition)
void ControlCore::SetHoveredCell(Core::Point pos)
{
_updateHoveredCell(std::optional<til::point>{ pos });
}
void ControlCore::ClearHoveredCell()
{
_updateHoveredCell(std::nullopt);
}
void ControlCore::_updateHoveredCell(const std::optional<til::point> terminalPosition)
{
if (terminalPosition == _lastHoveredCell)
{
@@ -477,7 +486,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return winrt::hstring{ _terminal->GetHyperlinkAtPosition(pos) };
}
winrt::hstring ControlCore::GetHoveredUriText() const
winrt::hstring ControlCore::HoveredUriText() const
{
auto lock = _terminal->LockForReading(); // Lock for the duration of our reads.
if (_lastHoveredCell.has_value())
@@ -487,9 +496,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return {};
}
std::optional<til::point> ControlCore::GetHoveredCell() const
Windows::Foundation::IReference<Core::Point> ControlCore::HoveredCell() const
{
return _lastHoveredCell;
return _lastHoveredCell.has_value() ? Windows::Foundation::IReference<Core::Point>{ _lastHoveredCell.value() } : nullptr;
}
// Method Description:
@@ -594,9 +603,34 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetFontInfo(_actualFont);
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
if (_renderEngine)
{
std::unordered_map<std::wstring_view, uint32_t> featureMap;
if (const auto fontFeatures = _settings.FontFeatures())
{
featureMap.reserve(fontFeatures.Size());
for (const auto& [tag, param] : fontFeatures)
{
featureMap.emplace(tag, param);
}
}
std::unordered_map<std::wstring_view, float> axesMap;
if (const auto fontAxes = _settings.FontAxes())
{
axesMap.reserve(fontAxes.Size());
for (const auto& [axis, value] : fontAxes)
{
axesMap.emplace(axis, value);
}
}
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
LOG_IF_FAILED(_renderEngine->UpdateDpi(newDpi));
LOG_IF_FAILED(_renderEngine->UpdateFont(_desiredFont, _actualFont, featureMap, axesMap));
}
// If the actual font isn't what was requested...
if (_actualFont.GetFaceName() != _desiredFont.GetFaceName())
@@ -895,6 +929,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _actualFont;
}
winrt::Windows::Foundation::Size ControlCore::FontSize() const noexcept
{
const auto fontSize = GetFont().GetSize();
return {
::base::saturated_cast<float>(fontSize.X),
::base::saturated_cast<float>(fontSize.Y)
};
}
winrt::hstring ControlCore::FontFaceName() const noexcept
{
return winrt::hstring{ GetFont().GetFaceName() };
}
uint16_t ControlCore::FontWeight() const noexcept
{
return static_cast<uint16_t>(GetFont().GetWeight());
}
til::size ControlCore::FontSizeInDips() const
{
const til::size fontSize{ GetFont().GetSize() };
@@ -1077,10 +1129,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _settings.CopyOnSelect();
}
std::vector<std::wstring> ControlCore::SelectedText(bool trimTrailingWhitespace) const
Windows::Foundation::Collections::IVector<winrt::hstring> ControlCore::SelectedText(bool trimTrailingWhitespace) const
{
// RetrieveSelectedTextFromBuffer will lock while it's reading
return _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text;
const auto internalResult{ _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text };
auto result = winrt::single_threaded_vector<winrt::hstring>();
for (const auto& row : internalResult)
{
result.Append(winrt::hstring{ row });
}
return result;
}
::Microsoft::Console::Types::IUiaData* ControlCore::GetUiaData() const
@@ -1124,7 +1184,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void ControlCore::SetBackgroundOpacity(const float opacity)
void ControlCore::SetBackgroundOpacity(const double opacity)
{
if (_renderEngine)
{
@@ -1176,7 +1236,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
HANDLE ControlCore::GetSwapChainHandle() const
uint64_t ControlCore::SwapChainHandle() const
{
// This is called by:
// * TermControl::RenderEngineSwapChainChanged, who is only registered
@@ -1184,7 +1244,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// * TermControl::_InitializeTerminal, after the call to Initialize, for
// _AttachDxgiSwapChainToXaml.
// In both cases, we'll have a _renderEngine by then.
return _renderEngine->GetSwapChainHandle();
return reinterpret_cast<uint64_t>(_renderEngine->GetSwapChainHandle());
}
void ControlCore::_rendererWarning(const HRESULT hr)

View File

@@ -48,15 +48,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void UpdateAppearance(const IControlAppearance& newAppearance);
void SizeChanged(const double width, const double height);
void ScaleChanged(const double scale);
HANDLE GetSwapChainHandle() const;
uint64_t SwapChainHandle() const;
void AdjustFontSize(int fontSizeDelta);
void ResetFontSize();
FontInfo GetFont() const;
til::size FontSizeInDips() const;
winrt::Windows::Foundation::Size FontSize() const noexcept;
winrt::hstring FontFaceName() const noexcept;
uint16_t FontWeight() const noexcept;
til::color BackgroundColor() const;
void SetBackgroundOpacity(const float opacity);
void SetBackgroundOpacity(const double opacity);
void SendInput(const winrt::hstring& wstr);
void PasteText(const winrt::hstring& hstr);
@@ -67,10 +71,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ResumeRendering();
void UpdatePatternLocations();
void UpdateHoveredCell(const std::optional<til::point>& terminalPosition);
void SetHoveredCell(Core::Point terminalPosition);
void ClearHoveredCell();
winrt::hstring GetHyperlink(const til::point position) const;
winrt::hstring GetHoveredUriText() const;
std::optional<til::point> GetHoveredCell() const;
winrt::hstring HoveredUriText() const;
Windows::Foundation::IReference<Core::Point> HoveredCell() const;
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
@@ -119,7 +124,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool HasSelection() const;
bool CopyOnSelect() const;
std::vector<std::wstring> SelectedText(bool trimTrailingWhitespace) const;
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
void SetSelectionAnchor(til::point const& position);
void SetEndSelectionPoint(til::point const& position);
@@ -232,6 +237,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _raiseReadOnlyWarning();
void _updateAntiAliasingMode(::Microsoft::Console::Render::DxEngine* const dxEngine);
void _connectionOutputHandler(const hstring& hstr);
void _updateHoveredCell(const std::optional<til::point> terminalPosition);
friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;

View File

@@ -8,9 +8,97 @@ import "EventArgs.idl";
namespace Microsoft.Terminal.Control
{
// This is a mirror of
// ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState,
// but projectable.
// !! LOAD BEARING !! If you make this a struct with Booleans (like they
// make the most sense as), then the app will crash trying to toss one of
// these across the process boundary. I haven't the damndest idea why.
[flags]
enum MouseButtonState
{
IsLeftButtonDown = 0x1,
IsMiddleButtonDown = 0x2,
IsRightButtonDown = 0x4
};
[default_interface] runtimeclass ControlCore : ICoreState
{
ControlCore(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
Boolean Initialize(Double actualWidth,
Double actualHeight,
Double compositionScale);
void UpdateSettings(IControlSettings settings);
void UpdateAppearance(IControlAppearance appearance);
UInt64 SwapChainHandle { get; };
Windows.Foundation.Size FontSize { get; };
String FontFaceName { get; };
UInt16 FontWeight { get; };
Boolean TrySendKeyEvent(Int16 vkey,
Int16 scanCode,
Microsoft.Terminal.Core.ControlKeyStates modifiers,
Boolean keyDown);
Boolean SendCharEvent(Char ch,
Int16 scanCode,
Microsoft.Terminal.Core.ControlKeyStates modifiers);
void SendInput(String text);
void PasteText(String text);
void SetHoveredCell(Microsoft.Terminal.Core.Point terminalPosition);
void ClearHoveredCell();
void ResetFontSize();
void AdjustFontSize(Int32 fontSizeDelta);
void SizeChanged(Double width, Double height);
void ScaleChanged(Double scale);
void ToggleShaderEffects();
void ToggleReadOnlyMode();
Microsoft.Terminal.Core.Point CursorPosition { get; };
void ResumeRendering();
void BlinkAttributeTick();
void UpdatePatternLocations();
void Search(String text, Boolean goForward, Boolean caseSensitive);
void SetBackgroundOpacity(Double opacity);
Microsoft.Terminal.Core.Color BackgroundColor { get; };
Boolean HasSelection { get; };
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
String HoveredUriText { get; };
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
void Close();
void BlinkCursor();
Boolean IsInReadOnlyMode { get; };
Boolean CursorOn;
void EnablePainting();
event FontSizeChangedEventArgs FontSizeChanged;
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> CursorPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ConnectionStateChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> HoveredHyperlinkChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
event Windows.Foundation.TypedEventHandler<Object, Object> SwapChainChanged;
event Windows.Foundation.TypedEventHandler<Object, RendererWarningArgs> RendererWarning;
event Windows.Foundation.TypedEventHandler<Object, NoticeEventArgs> RaiseNotice;
event Windows.Foundation.TypedEventHandler<Object, TransparencyChangedEventArgs> TransparencyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
};
}

View File

@@ -8,13 +8,15 @@
#include <unicode.hpp>
#include <Utf16Parser.hpp>
#include <Utils.h>
#include <WinUser.h>
#include <LibraryResources.h>
#include "../../types/inc/GlyphWidth.hpp"
#include "../../types/inc/Utils.hpp"
#include "../../buffer/out/search.h"
#include "InteractivityAutomationPeer.h"
#include "ControlInteractivity.g.cpp"
#include "TermControl.h"
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Console::VirtualTerminal;
@@ -27,6 +29,15 @@ static constexpr unsigned int MAX_CLICK_COUNT = 3;
namespace winrt::Microsoft::Terminal::Control::implementation
{
static constexpr TerminalInput::MouseButtonState toInternalMouseState(const Control::MouseButtonState& state)
{
return TerminalInput::MouseButtonState{
WI_IsFlagSet(state, MouseButtonState::IsLeftButtonDown),
WI_IsFlagSet(state, MouseButtonState::IsMiddleButtonDown),
WI_IsFlagSet(state, MouseButtonState::IsRightButtonDown)
};
}
ControlInteractivity::ControlInteractivity(IControlSettings settings,
TerminalConnection::ITerminalConnection connection) :
_touchAnchor{ std::nullopt },
@@ -37,6 +48,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core = winrt::make_self<ControlCore>(settings, connection);
}
// Method Description:
// - Updates our internal settings. These settings should be
// interactivity-specific. Right now, we primarily update _rowsToScroll
// with the current value of SPI_GETWHEELSCROLLLINES.
// Arguments:
// - <none>
// Return Value:
// - <none>
void ControlInteractivity::UpdateSettings()
{
_updateSystemParameterSettings();
@@ -50,9 +69,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_multiClickTimer = GetDoubleClickTime() * 1000;
}
winrt::com_ptr<ControlCore> ControlInteractivity::GetCore()
Control::ControlCore ControlInteractivity::Core()
{
return _core;
return *_core;
}
// Method Description:
@@ -85,11 +104,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _multiClickCounter;
}
void ControlInteractivity::GainFocus()
void ControlInteractivity::GotFocus()
{
if (_uiaEngine.get())
{
THROW_IF_FAILED(_uiaEngine->Enable());
}
_updateSystemParameterSettings();
}
void ControlInteractivity::LostFocus()
{
if (_uiaEngine.get())
{
THROW_IF_FAILED(_uiaEngine->Disable());
}
}
// Method Description
// - Updates internal params based on system parameters
void ControlInteractivity::_updateSystemParameterSettings() noexcept
@@ -156,7 +188,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core->PasteText(winrt::hstring{ wstr });
}
void ControlInteractivity::PointerPressed(TerminalInput::MouseButtonState buttonState,
void ControlInteractivity::PointerPressed(Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const uint64_t timestamp,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
@@ -170,7 +202,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// GH#9396: we prioritize hyper-link over VT mouse events
auto hyperlink = _core->GetHyperlink(terminalPosition);
if (buttonState.isLeftButtonDown &&
if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown) &&
ctrlEnabled && !hyperlink.empty())
{
const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
@@ -182,9 +214,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else if (_canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
const auto adjustment = _core->ScrollOffset() > 0 ? _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight() : 0;
// If the click happened outside the active region, just don't send any mouse event
if (const auto adjustedY = terminalPosition.y() - adjustment; adjustedY >= 0)
{
_core->SendMouseEvent({ terminalPosition.x(), adjustedY }, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
}
}
else if (buttonState.isLeftButtonDown)
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
{
const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
// This formula enables the number of clicks to cycle properly
@@ -219,7 +256,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_singleClickTouchdownPos = std::nullopt;
}
}
else if (buttonState.isRightButtonDown)
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsRightButtonDown))
{
// CopyOnSelect right click always pastes
if (_core->CopyOnSelect() || !_core->HasSelection())
@@ -238,7 +275,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_touchAnchor = contactPoint;
}
void ControlInteractivity::PointerMoved(TerminalInput::MouseButtonState buttonState,
void ControlInteractivity::PointerMoved(Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const bool focused,
@@ -249,9 +286,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Short-circuit isReadOnly check to avoid warning dialog
if (focused && !_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
}
else if (focused && buttonState.isLeftButtonDown)
else if (focused && WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
{
if (_singleClickTouchdownPos)
{
@@ -279,7 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
SetEndSelectionPoint(pixelPosition);
}
_core->UpdateHoveredCell(terminalPosition);
_core->SetHoveredCell(terminalPosition);
}
void ControlInteractivity::TouchMoved(const til::point newTouchPoint,
@@ -319,7 +356,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void ControlInteractivity::PointerReleased(TerminalInput::MouseButtonState buttonState,
void ControlInteractivity::PointerReleased(Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const til::point pixelPosition)
@@ -328,7 +365,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Short-circuit isReadOnly check to avoid warning dialog
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
return;
}
@@ -366,7 +403,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const til::point pixelPosition,
const TerminalInput::MouseButtonState state)
const Control::MouseButtonState buttonState)
{
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
@@ -382,7 +419,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WM_MOUSEWHEEL,
modifiers,
::base::saturated_cast<short>(delta),
state);
toInternalMouseState(buttonState));
}
const auto ctrlPressed = modifiers.IsCtrlPressed();
@@ -398,7 +435,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else
{
_mouseScrollHandler(delta, pixelPosition, state.isLeftButtonDown);
_mouseScrollHandler(delta, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
}
return false;
}
@@ -557,4 +594,36 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Convert the location in pixels to characters within the current viewport.
return til::point{ pixelPosition / fontSize };
}
// Method Description:
// - Creates an automation peer for the Terminal Control, enabling
// accessibility on our control.
// - Our implementation implements the ITextProvider pattern, and the
// IControlAccessibilityInfo, to connect to the UiaEngine, which must be
// attached to the core's renderer.
// - The TermControlAutomationPeer will connect this to the UI tree.
// Arguments:
// - None
// Return Value:
// - The automation peer for our control
Control::InteractivityAutomationPeer ControlInteractivity::OnCreateAutomationPeer()
try
{
auto autoPeer = winrt::make_self<implementation::InteractivityAutomationPeer>(this);
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get());
_core->AttachUiaEngine(_uiaEngine.get());
return *autoPeer;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return nullptr;
}
::Microsoft::Console::Types::IUiaData* ControlInteractivity::GetUiaData() const
{
return _core->GetUiaData();
}
}

View File

@@ -23,11 +23,6 @@
#include "ControlCore.h"
namespace Microsoft::Console::VirtualTerminal
{
struct MouseButtonState;
}
namespace ControlUnitTests
{
class ControlCoreTests;
@@ -42,20 +37,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
ControlInteractivity(IControlSettings settings,
TerminalConnection::ITerminalConnection connection);
void GainFocus();
void GotFocus();
void LostFocus();
void UpdateSettings();
void Initialize();
winrt::com_ptr<ControlCore> GetCore();
Control::ControlCore Core();
Control::InteractivityAutomationPeer OnCreateAutomationPeer();
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
#pragma region Input Methods
void PointerPressed(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
void PointerPressed(Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const uint64_t timestamp,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const til::point pixelPosition);
void TouchPressed(const til::point contactPoint);
void PointerMoved(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
void PointerMoved(Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const bool focused,
@@ -63,7 +62,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TouchMoved(const til::point newTouchPoint,
const bool focused);
void PointerReleased(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
void PointerReleased(Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const til::point pixelPosition);
@@ -72,7 +71,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const til::point pixelPosition,
const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
const Control::MouseButtonState state);
void UpdateScrollbar(const double newValue);
@@ -83,7 +82,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(const til::point pixelPosition);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
private:
// NOTE: _uiaEngine must be ordered before _core.
//
// ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own.
// We must ensure that we first destroy the ControlCore before the UiaEngine instance
// in order to safely resolve this unsafe pointer dependency. Otherwise a deallocated
// IRenderEngine is accessed when ControlCore calls Renderer::TriggerTeardown.
// (C++ class members are destroyed in reverse order.)
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
winrt::com_ptr<ControlCore> _core{ nullptr };
unsigned int _rowsToScroll;
double _internalScrollbarPosition{ 0.0 };
@@ -129,10 +141,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _sendPastedTextToConnection(std::wstring_view wstr);
til::point _getTerminalPosition(const til::point& pixelPosition);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;
};

View File

@@ -5,6 +5,8 @@ import "ICoreState.idl";
import "IControlSettings.idl";
import "ControlCore.idl";
import "EventArgs.idl";
import "InteractivityAutomationPeer.idl";
namespace Microsoft.Terminal.Control
{
@@ -13,5 +15,51 @@ namespace Microsoft.Terminal.Control
{
ControlInteractivity(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
ControlCore Core { get; };
void UpdateSettings();
void Initialize();
void GotFocus();
void LostFocus();
InteractivityAutomationPeer OnCreateAutomationPeer();
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(Microsoft.Terminal.Core.Point point);
void PointerPressed(MouseButtonState buttonState,
UInt32 pointerUpdateKind,
UInt64 timestamp,
Microsoft.Terminal.Core.ControlKeyStates modifiers,
Microsoft.Terminal.Core.Point pixelPosition);
void TouchPressed(Microsoft.Terminal.Core.Point contactPoint);
void PointerMoved(MouseButtonState buttonState,
UInt32 pointerUpdateKind,
Microsoft.Terminal.Core.ControlKeyStates modifiers,
Boolean focused,
Microsoft.Terminal.Core.Point pixelPosition);
void TouchMoved(Microsoft.Terminal.Core.Point newTouchPoint,
Boolean focused);
void PointerReleased(MouseButtonState buttonState,
UInt32 pointerUpdateKind,
Microsoft.Terminal.Core.ControlKeyStates modifiers,
Microsoft.Terminal.Core.Point pixelPosition);
void TouchReleased();
Boolean MouseWheel(Microsoft.Terminal.Core.ControlKeyStates modifiers,
Int32 delta,
Microsoft.Terminal.Core.Point pixelPosition,
MouseButtonState state);
void UpdateScrollbar(Double newValue);
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
};
}

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