This implements `SetForceFullRepaintRendering` and adds a new
`SetGraphicsAPI` function. The former toggles `Present1` on and off
and the latter allows users to explicitly request Direct2D/3D.
On top of these changes I did a minor cleanup of the interface,
because now that DxRenderer is gone we don't need all that anymore.
Closes#14254Closes#16747
## Validation Steps Performed
* Toggling Direct2D on/off changes colored ligature support ✅
* Toggling Present1 on/off can be observed in a debugger ✅
* Toggling WARP on/off changes GPU metrics ✅
---------
Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
This PR automagically finds and replaces all[^1] usages of our
TYPED_EVENT macro with `til::event`. Benefits include:
* less macro magic
* editors are more easily able to figure out the relationship between
`til::event<> Foo;`, `Foo.raise(...)`, and `bar.Foo({this,
&Bar::FooHandler})` (whereas before the relationship between
`_FooHandlers(...)` and `bar.Foo({...})`) couldn't be figured out by
vscode & sublime.
Other find & replace work that had to be done:
* I added the `til::typed_event<>` == `<IInspectable, IInspectable>`
thing from #16170, since that is goodness
* I actually fixed `til::property_changed_event`, so you can use that
for your property changed events. They're all the same anyways.
* events had to come before `WINRT_PROPERTY`s, since the latter macro
leaves us in a `private:` block
* `Pane::SetupChildCloseHandlers` I had to swap _back_, because the
script thought that was an event 🤦
* `ProfileViewModel::DeleteProfile` had to be renamed
`DeleteProfileRequested`, since there was already a `DeleteProfile`
method on it.
* WindowManager.cpp was directly wiring up it's `winrt::event`s to the
monarch & peasant. That doesn't work with `til::event`s and I'm kinda
surprised it ever did
<details>
<summary>The script in question</summary>
```py
import os
import re
def replace_in_file(file_path, file_name):
with open(file_path, 'r', encoding="utf8") as file:
content = file.read()
found_matches = False
# Define the pattern for matching
pattern = r' WINRT_CALLBACK\((\w+),\s*(.*?)\);'
event_matches = re.findall(pattern, content)
if event_matches:
found_matches = True
print(f'found events in {file_path}:')
for match in event_matches:
name = match[0]
args = match[1]
if name == "newConnection" and file_name == "ConptyConnection.cpp":
# This one is special
continue
old_declaration = 'WINRT_CALLBACK(' + name + ', ' + args + ');'
new_declaration = 'til::event<' + args + '> ' + name + ';' if name != "PropertyChanged" else 'til::property_changed_event PropertyChanged;'
print(f' {old_declaration} -> {new_declaration}')
content = content.replace(old_declaration, new_declaration)
typed_event_pattern = r' TYPED_EVENT\((\w+),\s*(.*?)\);'
typed_matches = re.findall(typed_event_pattern, content)
if typed_matches:
found_matches = True
print(f'found typed_events in {file_path}:')
for match in typed_matches:
name = match[0]
args = match[1]
if name == "newConnection" and file_name == "ConptyConnection.cpp":
# This one is special
continue
old_declaration = f'TYPED_EVENT({name}, {args});'
was_inspectable = (args == "winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable" ) or (args == "IInspectable, IInspectable" )
new_declaration = f'til::typed_event<{args}> {name};' if not was_inspectable else f"til::typed_event<> {name};"
print(f' {old_declaration} -> {new_declaration}')
content = content.replace(old_declaration, new_declaration)
handlers_pattern = r'_(\w+)Handlers\('
handler_matches = re.findall(handlers_pattern, content)
if handler_matches:
found_matches = True
print(f'found handlers in {file_path}:')
for match in handler_matches:
name = match
if name == "newConnection" and file_name == "ConptyConnection.cpp":
# This one is special
continue
old_declaration = f'_{name}Handlers('
new_declaration = f'{name}.raise('
print(f' {old_declaration} -> {new_declaration}')
content = content.replace(old_declaration, new_declaration)
if found_matches:
with open(file_path, 'w', encoding="utf8") as file:
file.write(content)
def find_and_replace(directory):
for root, dirs, files in os.walk(directory):
if 'Generated Files' in dirs:
dirs.remove('Generated Files') # Exclude the "Generated Files" directory
for file in files:
if file.endswith('.cpp') or file.endswith('.h') or file.endswith('.hpp'):
file_path = os.path.join(root, file)
try:
replace_in_file(file_path, file)
except Exception as e:
print(f"error reading {file_path}")
if file == "TermControl.cpp":
print(e)
# raise e
# Replace in files within a specific directory
directory_path = 'D:\\dev\\public\\terminal\\src'
find_and_replace(directory_path)
```
</details>
[^1]: there are other macros we use that were also using this macro,
those weren't replaced.
---------
Co-authored-by: Dustin Howett <duhowett@microsoft.com>
Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
This pull request introduces support for disabling full-color emoji (and
technically other COLR-related font features!)
Full-color emoji don't respond to SGR colors, intensity, faint or blink.
Some users also just prefer the line art ones.
Related to #15979
Refs #1790Closes#956
I realize I might be one of the few developers that care about custom
shader support in terminal but I thought it's worth proposing it and see
what you think.
This is to support custom shaders with custom textures.
I was thinking of exposing the background image to the shader but that
felt complicated after looking into it.
I have tested exploratively. I think the texture loader is possible to
unit test so that is a possible improvement.
The error reporting (as with other custom pixel shader code) is not very
good. That is also an area that I could improve upon.
I do think the risk of adding this is rather low as the new code is only
executed when experimental.pixelShaderImagePath is set.
### Details
Only added to the Atlas engine.
Instead I load the texture using WIC into a shader resource view. When
binding shader resources I test for presence of custom texture and bind
it to register t1.
The image loading code was found in [the D3D Texture documentation].
It's a mouthful but seems rather robust.
Tested setting: "experimental.pixelShaderImagePath"
1. Tested not specifying it.
2. Tested setting it.
3. Tested changing it (the changes are picked up)
4. Tested invalid path
5. Tested a custom shader that made use of the custom texture.
[the D3D Texture documentation]: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-textures-how-to
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
This adds support for drawing our own box drawing, block element,
and basic Powerline (U+E0Bx) glyphs in AtlasEngine.
This PR consists of 4 parts:
* AtlasEngine was refactored to simplify `_drawGlyph` because I've
wanted to do that for ~1 year now and never got a chance.
Well, now I'm doing it and you all will review it muahahaha.
The good news is that it removes a goto usage that even for my
standards was rather dangerous. Now it's gone and the risk with it.
* AtlasEngine was further refactored to properly parse out text that
we want to handle different from regular text. Previously, we only
did that for soft fonts, but now we want to do that for a lot more,
so a refactor was in order. The new code is still extremely
disgusting, because I now stuff `wchar_t`s into an array that's
intended for glyph indices, but that's the best way to make it fast
and not blow up the complexity of the code even further.
* Finally this adds a huge LUT for all the aforementioned glyphs.
The LUT has 4 "drawing instruction" entries per glyph which describe
the shape (rectangle, circle, lines, etc.) and the start/end coord.
With a lot of bit packing each entry is only 4 bytes large.
* Finally-finally a `builtinGlyphs` setting was added to the font
object and it defaults to `true`.
Closes#5897
## Validation Steps Performed
* RenderingTests with soft fonts ✅
* All the aforementioned glyphs ✅
* ...with color ✅
* `customGlyphs` setting can be toggled on and off ✅
With AtlasEngine being fairly stable at this point and being enabled
by default in the 1.19 branch, this changeset removes DxEngine.
## Validation Steps Performed
* WT builds and runs ✅
* WpfTestNetCore ✅
* Saving the config removes the `useAtlasEngine` key ✅
`TextBuffer::GenHTML` and `TextBuffer::GenRTF` now read directly from
the TextBuffer.
- Since we're reading from the buffer, we can now read _all_ the
attributes saved in the buffer. Formatted copy now copies most (if not
all) font/color attributes in the requested format (RTF/HTML).
- Use `TextBuffer::CopyRequest` to pass all copy-related options into
text generation functions as one unit.
- Helper function `TextBuffer::CopyRequest::FromConfig()` generates a
copy request based on Selection mode and user configuration.
- Both formatted text generation functions now use `std::string` and
`fmt::format_to` to generate the required strings. Previously, we were
using `std::ostringstream` which is not recommended due to its potential
overhead.
- Reading attributes from `ROW`'s attribute RLE simplified the logic as
we don't have to track attribute change between the text.
- On the caller side, we do not have to rebuild the plain text string
from the vector of strings anymore. `TextBuffer::GetPlainText()` returns
the entire text as one `std::string`.
- Removed `TextBuffer::TextAndColors`.
- Removed `TextBuffer::GetText()`. `TextBuffer::GetPlainText()` took its
place.
This PR also fixes two bugs in the formatted copy:
- We were applying line breaks after each selected row, even though the
row could have been a Wrapped row. This caused the wrapped rows to break
when they shouldn't.
- We mishandled Unicode text (\uN) within the RTF copy. Every next
character that uses a surrogate pair or high codepoint was missing in
the copied text when pasted to MSWord. The command `\uc4` should have
been `\uc1`, which is used to tell how many fallback characters are used
for each Unicode codepoint (\u). We always use one `?` character as the
fallback.
Closes#16191
**References and Relevant Issues**
- #16270
**Validation Steps Performed**
- Casual copy-pasting from Terminal or OpenConsole to word editors works
as before.
- Verified HTML copy by copying the generated HTML string and running it
through an HTML viewer.
[Sample](https://codepen.io/tusharvickey/pen/wvNXbVN)
- Verified RTF copy by copy-pasting the generated RTF string into
MSWord.
- SingleLine mode works (<kbd>Shift</kbd>+ copy)
- BlockSelection mode works (<kbd>Alt</kbd> selection)
Avoid generating extra formatted copies when action's `copyFormatting`
is not present and globally set `copyFormatting` is used.
Previously, when the action's `copyFormatting` wasn't set we deferred
the decision of which formats needed to be copied to the
`TerminalPage::CopyToClipboard` handler. This meant we needed to copy
the text in all the available formats and pass it to the handler to copy
the required formats after querying the global `copyFormatting`.
To avoid making extra copies, we'll store the global `copyFormatting` in
TerminalSettings and pass it down to `TermControl`. If
`ControlCore::CopySelectionToClipboard()` doesn't receive action
specific `copyFormatting`, it will fall back to the global one _before
generating the texts_.
## Validation Steps Performed
- no `copyFormatting` set for the copy action: Copies formats according
to the global `copyFormatting`.
- `copyFormatting` is set for the copy action: Copies formats according
to the action's `copyFormatting`.
**FIRST TIME CONTRIBUTOR**
Follows the existing selection code as much as possible.
Updated logic that finds selection rectangles to also identify search
rectangles.
Right now, this feature only works in the new Atlas engine -- it uses
the background and foreground color bitmaps to quickly and efficiently
set the colors of a whole region of text.
Closes#7561
Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
This fixes a number of bugs introduced in 4370da9, all of which are of
the same kind: Holding the terminal lock across `WriteFile` calls into
the ConPTY pipe. This is problematic, because the pipe has a tiny buffer
size of just 4KiB and ConPTY may respond on its output pipe, before the
entire buffer given to `WriteFile` has been emptied. When the ConPTY
output thread then tries to acquire the terminal lock to begin parsing
the VT output, we get ourselves a proper deadlock (cross process too!).
The solution is to tease `Terminal` further apart into code that is
thread-safe and code that isn't. Functions like `SendKeyEvent` so far
have mixed them into one, because when they get called by `ControlCore`
they both, processed the data (not thread-safe as it accesses VT state)
and also sent that data back into `ControlCore` through a callback
which then indirectly called into the `ConptyConnection` which calls
`WriteFile`. Instead, we now return the data that needs to be sent from
these functions, and `ControlCore` is free to release the lock and
then call into the connection, which may then block indefinitely.
## Validation Steps Performed
* Start nvim in WSL
* Press `i` to enter the regular Insert mode
* Paste 1MB of text
* Doesn't deadlock ✅
This commit fixes 2 issues:
* `ControlCore::ScrollMarks()` would call `ResetIfStale`
again while the search prompt hasn't changed.
This has been fixed by using `_cachedSearchResultRows` as
the indicator for whether it needs to be recreated or not.
* While typing a search query, the selection would move among the
results with each typed character, because `MovePastCurrentSelection`
would do what its name indicates. It has been renamed and rewritten
to be `MoveToCurrentSelection`. To avoid breaking UIA, the previous
`MovePastPoint` implementation was kept.
Since the new `MoveToCurrentSelection` function would not move past the
current selection anymore, changing the direction would not move past
the current result either. To fix this, we now don't invalidate the
search cache when changing the direction.
Closes#15954
## Validation Steps Performed
* Run ``"helloworld`n"*20`` in pwsh
* Search for "helloworld"
* While typing the characters the selection doesn't move ✅
* ...nor when searching downwards ✅
* ...nor when erasing parts of it ✅
* ...and it behaves identical in conhost ✅
This replaces the use of a `<Canvas>` with an `<Image>` for drawing
scrollbar marks. Otherwise, WinUI struggles with the up to ~9000 UI
elements as they get dirtied every time the scrollbar moves.
(FWIW 9000 is not a lot and it should not struggle with that.)
The `<Image>` element has the benefit that we can get hold of a CPU-side
bitmap which we can manually draw our marks into and then swap them into
the UI tree. It draws the same 9000 elements, but now WinUI doesn't
struggle anymore because only 1 element gets invalidated every time.
Closes#15955
## Validation Steps Performed
* Fill the buffer with "e"
* Searching for "e" fills the entire thumb range with white ✅
* ...doesn't lag when scrolling around ✅
* ...updates quickly when adding newlines at the end ✅
* Marks sort of align with their scroll position ✅
`Terminal` is used concurrently by at least 4 threads. The table
below lists the class members and the threads that access them
to the best of my knowledge. Where:
* UI: UI Thread
* BG: Background worker threads (`winrt::resume_background`)
* RD: Render thread
* VT: VT connection thread
| | UI | BG | RD | VT |
|------------------------------------|----|----|----|----|
| `_pfnWriteInput` | x | x | | x |
| `_pfnWarningBell` | | | | x |
| `_pfnTitleChanged` | | | | x |
| `_pfnCopyToClipboard` | | | | x |
| `_pfnScrollPositionChanged` | x | x | | x |
| `_pfnCursorPositionChanged` | | | | x |
| `_pfnTaskbarProgressChanged` | | | | x |
| `_pfnShowWindowChanged` | | | | x |
| `_pfnPlayMidiNote` | | | | x |
| `_pfnCompletionsChanged` | | | | x |
| `_renderSettings` | x | | x | x |
| `_stateMachine` | x | | | x |
| `_terminalInput` | x | | | x |
| `_title` | x | | x | x |
| `_startingTitle` | x | | x | |
| `_startingTabColor` | x | | | |
| `_defaultCursorShape` | x | | | x |
| `_systemMode` | | x | x | x |
| `_snapOnInput` | x | x | | |
| `_altGrAliasing` | x | | | |
| `_suppressApplicationTitle` | x | | | x |
| `_trimBlockSelection` | x | | | |
| `_autoMarkPrompts` | x | | | |
| `_taskbarState` | x | | | x |
| `_taskbarProgress` | x | | | x |
| `_workingDirectory` | x | | | x |
| `_fontInfo` | x | | x | |
| `_selection` | x | x | x | x |
| `_blockSelection` | x | x | x | |
| `_wordDelimiters` | x | x | | |
| `_multiClickSelectionMode` | x | x | x | |
| `_selectionMode` | x | x | x | |
| `_selectionIsTargetingUrl` | x | x | x | |
| `_selectionEndpoint` | x | x | x | |
| `_anchorInactiveSelectionEndpoint` | x | x | x | |
| `_mainBuffer` | x | x | x | x |
| `_altBuffer` | x | x | x | x |
| `_mutableViewport` | x | | x | x |
| `_scrollbackLines` | x | | | |
| `_detectURLs` | x | | | |
| `_altBufferSize` | x | x | x | x |
| `_deferredResize` | x | | | x |
| `_scrollOffset` | x | x | x | x |
| `_patternIntervalTree` | x | x | x | x |
| `_lastKeyEventCodes` | x | | | |
| `_currentPromptState` | x | | | x |
Only 7 members are specific to one thread and don't require locking.
All other members require some for of locking to be safe for use.
To address the issue this changeset adds `LockForReading/LockForWriting`
calls everywhere `_terminal` is accessed in `ControlCore/HwndTerminal`.
Additionally, to ensure these issues don't pop up anymore, it adds to
all `Terminal` functions a debug assertion that the lock is being held.
Finally, because this changeset started off rather modest, it contains
changes that I initially made without being aware about the extent of
the issue. It simplifies the access around `_patternIntervalTree` by
making `_InvalidatePatternTree()` directly use that member.
Furthermore, it simplifies `_terminal->SetCursorOn(!IsCursorOn())` to
`BlinkCursor()`, allowing the code to be shared with `HwndTerminal`.
Ideally `Terminal` should not be that much of a class so that we don't
need such coarse locking. Splitting out selection and rendering state
should allow deduplicating code with conhost and use finer locking.
Closes#9617
## Validation Steps Performed
I tried to use as many Windows Terminal features as I could and fixed
every occurrence of `_assertLocked()` failures.
## Summary of the Pull Request
Closes#7158
Enabling Acrylic as both an appearance setting (with all the plumbing),
allowing it to be set differently in both focused and unfocused
terminals. EnableUnfocusedAcrylic Global Setting that controls if
unfocused acrylic is possible so that people can disable that behavior.
## References and Relevant Issues
#7158 , references: #15913 , #11092
## Detailed Description of the Pull Request / Additional comments
### Allowing Acrylic to be set differently in both focused and unfocused
terminals:
#### A

#### B

#### C

#### D

``` json
"profiles":
{
"list":
[
{
"commandline": "pwsh.exe",
"name": "A",
"unfocusedAppearance":
{
"useAcrylic": true,
},
"useAcrylic": true,
},
{
"commandline": "pwsh.exe",
"name": "B",
"unfocusedAppearance":
{
"useAcrylic": false,
},
"useAcrylic": true,
},
{
"commandline": "pwsh.exe",
"name": "C",
"unfocusedAppearance":
{
"useAcrylic": true,
},
"useAcrylic": false,
},
{
"commandline": "pwsh.exe",
"name": "D",
"unfocusedAppearance":
{
},
"useAcrylic": false,
},
]
}
```
- **A**: AcrylicBlur always on
- **B**: Acrylic when focused, not acrylic when unfocused
- **C**: Why the hell not. Not Acrylic when focused, Acrylic when
unfocused.
- **D:** Possible today by not using Acrylic.
### EnableUnfocusedACrylic global setting that controls if unfocused
acrylic is possible
So that people can disable that behavior:

### Alternate approaches I considered:
Using `_InitializeBackgroundBrush` call instead of
`_changeBackgroundColor(bg) in
``TermControl::_UpdateAppearanceFromUIThread`. Comments in this function
mentioned:
``` *.cs'
// In the future, this might need to be changed to a
// _InitializeBackgroundBrush call instead, because we may need to
// switch from a solid color brush to an acrylic one.
```
I considered using this to tackle to problem, but don't see the benefit.
The only time we need to update the brush is when the user changes the
`EnableUnfocusedAcrylic ` setting which is already covered by
`fire_and_forget TermControl::UpdateControlSettings`
### Supporting different Opacity in Focused and Unfocused Appearance???
This PR is split up in two parts #7158 covers allowing Acrylic to be set
differently in both focused and unfocused terminals. And
EnableUnfocusedAcrylic Global Setting that controls if unfocused acrylic
is possible so that people can disable that behavior.
#11092 will be about enabling opacity as both an appearance setting,
allowing it to be set differently in both focused and unfocused
terminals.
### Skipping the XAML for now:
“I actually think we may want to skip the XAML on this one for now.
We've been having some discussions about compatibility settings, global
settings, stuff like this, and it might be _more- confusing to have you
do something here. We can always add it in post when we decide where to
put it.”
-- Mike Griese
## Validation Steps Performed
#### When Scrolling Mouse , opacity changes appropriately, on opacity
100 there are no gray lines or artefacts


#### When Adjusting Opacity through command palette, opacity changes
appropriately, on opacity 100 there are no gray lines or artefacts


#### When opening command palette state goes to unfocused, the acrylic
and color change appropriately


#### Stumbled upon a new bug when performing validation steps #15913

## PR Checklist
- [x] Closes#7158
- [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: #xxx
- [x] Schema updated (if necessary)
---------
Co-authored-by: Mike Griese <migrie@microsoft.com>
This is a resurrection of #8588. That PR became painfully stale after
the `ControlCore` split. Original description:
> ## Summary of the Pull Request
> This is a PoC for:
> * Search status in SearchBox (aka number of matches + index of the
current match)
> * Live search (aka search upon typing)
> ## Detailed Description of the Pull Request / Additional comments
> * Introduced this optionally (global setting to enable it)
> * The approach is following:
> * Every time the filter changes, enumerate all matches
> * Upon navigation just take the relevant match and select it
>
I cleaned it up a bit, and added support for also displaying the
positions of the matches in the scrollbar (if `showMarksOnScrollbar` is
also turned on).
It's also been made SUBSTANTIALLY easier after #15858 was merged.
Similar to before, searching while there's piles of output running isn't
_perfect_. But it's pretty awful currently, so that's not the end of the
world.
Gifs below.
* closes#8631 (which is a bullet point in #3920)
* closes#6319
Co-authored-by: Don-Vito <khvitaly@gmail.com>
---------
Co-authored-by: khvitaly <khvitaly@gmail.com>
The ultimate goal of this PR was to use ICU for text search to
* Improve Unicode support
Previously we used `towlower` and only supported BMP glphs.
* Improve search performance (10-100x)
This allows us to search for all results in the entire text buffer
at once without having to do so asynchronously.
Unfortunately, this required some significant changes too:
* ICU's search facilities operate on text positions which we need to be
mapped back to buffer coordinates. This required the introduction of
`CharToColumnMapper` to implement sort of a reverse-`_charOffsets`
mapping. It turns text (character) positions back into coordinates.
* Previously search restarted every time you clicked the search button.
It used the current selection as the starting position for the new
search. But since ICU's `uregex` cannot search backwards we're
required to accumulate all results in a vector first and so we
need to cache that vector in between searches.
* We need to know when the cached vector became invalid and so we have
to track any changes made to `TextBuffer`. The way this commit solves
it is by splitting `GetRowByOffset` into `GetRowByOffset` for
`const ROW` access and `GetMutableRowByOffset` which increments a
mutation counter on each call. The `Search` instance can then compare
its cached mutation count against the previous mutation count.
Finally, this commit makes 2 semi-unrelated changes:
* URL search now also uses ICU, since it's closely related to regular
text search anyways. This significantly improves performance at
large window sizes.
* A few minor issues in `UiaTracing` were fixed. In particular
2 functions which passed strings as `wstring` by copy are now
using `wstring_view` and `TraceLoggingCountedWideString`.
Related to #6319 and #8000
## Validation Steps Performed
* Search upward/downward in conhost ✅
* Search upward/downward in WT ✅
* Searching for any of ß, ẞ, ss or SS matches any of the other ✅
* Searching for any of Σ, σ, or ς matches any of the other ✅
I originally just wanted to close#1104, but then discovered that hey,
this event wasn't even used anymore. Excerpts of Teams convo:
* [Snap to character grid when resizing window by mcpiroman · Pull
Request #3181 · microsoft/terminal
(github.com)](https://github.com/microsoft/terminal/pull/3181/files#diff-d7ca72e0d5652fee837c06532efa614191bd5c41b18aa4d3ee6711f40138f04c)
added it to Tab.cpp
* where it was added
* which called `pane->Relayout` which I don't even REMEMBER
* By [Add functionality to open the Settings UI tab through openSettings
by leonMSFT · Pull Request #7802 · microsoft/terminal
(github.com)](https://github.com/microsoft/terminal/pull/7802/files#diff-83d260047bed34d3d9d5a12ac62008b65bd6dc5f3b9642905a007c3efce27efd),
there was seemingly no FontSizeChanged in Tab.cpp (when it got moved to
terminaltab.cpp)
> `Pane::Relayout` functionally did nothing because sizing was switched
to `star` sizing at some point in the past, so it was just deleted.
From [Misc pane refactoring by Rosefield · Pull Request #11373 ·
microsoft/terminal](https://github.com/microsoft/terminal/pull/11373/files#r736900998)
So, great. We can kill part of it, and convert the rest to a
`TypedEvent`, and get rid of `DECLARE_` / `DEFINE_`.
`ScrollPositionChangedEventArgs` was ALSO apparently already promoted to
a typed event, so kill that too.
_targets #14943_
When this is true, this will re-use the existing commandline to
pre-filter the results. This is especially helpful for completing a
suggestion based on the text that's already been typed.
Like with command history, this requires that shell integration is
enabled before it will work.## Summary of the Pull Request
## References and Relevant Issues
See also #13445
As spec'd in #14864
## Validation Steps Performed
Tested manually
This adds support for a new action, `showSuggestions`, as described in
#14864. This adds just one `source` currently, `recentCommands`. This
requires shell integration to be enabled in the shell to work properly.
When it is enabled, activating that action will invoke the suggestions
UI as a palette, populated with `sendInput` actions for each of the
user's recent commands.
* These don't persist across reboots.
* These are per-control.
There's mild plans to remedy that in a follow-up, though that needs a
bit more design consideration.
Closes#14779
## Summary of the Pull Request
This adds a new experimental per-setting to the terminal.
```ts
"experimental.repositionCursorWithMouse": bool
```
When:
* the setting is on
* AND you turn on shell integration (at least `133;B`)
* AND you click is somewhere _after_ the "active command" mark
we'll send a number of simulated keystrokes to the terminal based off
the number of cells between the place clicked and where the current
mouse cursor is.
## PR Checklist
- [ ] Related to #8573. I'm not marking as _closed_, because we should
probably polish this before we close that out. This is more a place to
start.
## Detailed Description of the Pull Request / Additional comments
There was a LOT of discussion in #8573. This is kinda a best effort
feature - it won't always work, but it should improve the experience
_most of the time_. We all kinda agreed that as much as the shell
probably should be responsible for doing this, there's myriad reasons
that won't work in practicality:
* That would also disable selection made by the terminal. That's a hard
sell.
* We'd need to invent some new mouse mode to support
click-to-reposition-but-drags-to-select-I-don't-want
* We'd then need shells to adopt that functionality.
And eventually settled that this was the least horrifying comprimise.
This has _e d g e c a s e s_:
* Does it work for wrapped lines? Well, kinda okay actually.
* Does it work for `vim`/`emacs`? Nope.
* Does it work for emoji/wide glyphs? I wouldn't expect it to! I mean,
emoji input is messed up anyways, right?
* Other characters like `ESC` (which are rendered by the shell as two
cells "^[")? Nope.
* Does it do selections? Nope.
* Clicking across lines with continuation prompts? Nope.
* Tabs? Nope.
* Wraps within tmux/screen? Nope.
https://github.com/xtermjs/xterm.js/blob/master/src/browser/input/MoveToCell.ts
has probably a more complete implementation of how we'd want to generate
the keypresses and such.
There's two parts to this PR that should be considered _separately_.
1. The Suggestions UI, a new graphical menu for displaying suggestions /
completions to the user in the context of the terminal the user is
working in.
2. The VsCode shell completions protocol. This enables the shell to
invoke this UI via a VT sequence.
These are being introduced at the same time, because they both require
one another. However, I need to absolutely emphasize:
### THE FORMAT OF THE COMPLETION PROTOCOL IS EXPERIMENTAL AND SUBJECT TO
CHANGE
This is what we've prototyped with VsCode, but we're still working on
how we want to conclusively define that protocol. However, we can also
refine the Suggestions UI independently of how the protocol is actually
implemented.
This will let us rev the Suggestions UI to support other things like
tooltips, recent commands, tasks, INDEPENDENTLY of us rev'ing the
completion protocol.
So yes, they're both here, but let's not nitpick that protocol for now.
### Checklist
* Doesn't actually close anything
* Heavily related to #3121, but I'm not gonna say that's closed till we
settle on the protocol
* See also:
* #1595
* #14779
* https://github.com/microsoft/vscode/pull/171648
### Detailed Description
#### Suggestions UI
The Suggestions UI is spec'ed over in #14864, so go read that. It's
basically a transient Command Palette, that floats by the user's cursor.
It's heavily forked from the Command Palette code, with all the business
about switching modes removed. The major bit of new code is
`SuggestionsControl::Anchor`. It also supports two "modes":
* A "palette", which is like the command palette - a list with a text
box
* A "menu", which is more like the intellisense flyout. No text box.
This is the mode that the shell completions use
#### Shell Completions Protocol
I literally cannot say this enough times - this protocol is experimental
and subject to change. Build on it at your own peril. It's disabled in
Release builds (but available in preview behind
`globals.experimental.enableShellCompletionMenu`), so that when it
ships, no one can take a dependency on it accidentally.
Right now we're just taking a blob of JSON, passing that up to the App
layer, who asks `Command` to parse it and build a list of `sendInput`
actions to populate the menu with. It's not a particularly elegant
solution, but it's good enough to prototype with.
#### How do I test this?
I've been testing this in two parts. You'll need a snippet in your
powershell profile, and a keybinding in the Terminal settings to trigger
it. The work together by binding <kbd>Ctrl+space</kbd> to _essentially_
send <kbd>F12</kbd><kbd>b</kbd>. Wacky, but it works.
```json
{ "command": { "action": "sendInput","input": "\u001b[24~b" }, "keys": "ctrl+space" },
```
```ps1
function Send-Completions2 {
$commandLine = ""
$cursorIndex = 0
# TODO: Since fuzzy matching exists, should completions be provided only for character after the
# last space and then filter on the client side? That would let you trigger ctrl+space
# anywhere on a word and have full completions available
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$commandLine, [ref]$cursorIndex)
$completionPrefix = $commandLine
# Get completions
$result = "`e]633;Completions"
if ($completionPrefix.Length -gt 0) {
# Get and send completions
$completions = TabExpansion2 -inputScript $completionPrefix -cursorColumn $cursorIndex
if ($null -ne $completions.CompletionMatches) {
$result += ";$($completions.ReplacementIndex);$($completions.ReplacementLength);$($cursorIndex);"
$result += $completions.CompletionMatches | ConvertTo-Json -Compress
}
}
$result += "`a"
Write-Host -NoNewLine $result
}
function Set-MappedKeyHandlers {
# VS Code send completions request (may override Ctrl+Spacebar)
Set-PSReadLineKeyHandler -Chord 'F12,b' -ScriptBlock {
Send-Completions2
}
}
# Register key handlers if PSReadLine is available
if (Get-Module -Name PSReadLine) {
Set-MappedKeyHandlers
}
```
### TODO
* [x] `(prompt | format-hex).`<kbd>Ctrl+space</kbd> -> This always
throws an exception. Seems like the payload is always clipped to
```{"CompletionText":"Ascii","ListItemText":"Ascii","ResultType":5,"ToolTip":"string
Ascii { get```
and that ain't JSON. Investigate on the pwsh side?
`IInputEvent` makes adding Unicode support to `InputBuffer` more
difficult than necessary as the abstract class makes downcasting
as well as copying quite verbose. I found that using `INPUT_RECORD`s
directly leads to a significantly simplified implementation.
In addition, this commit fixes at least one bug: The previous approach
to detect the null key via `DoActiveModifierKeysMatch` didn't work.
As it compared the modifier keys as a bitset with `==` it failed to
match whenever the numpad key was set, which it usually is.
## Validation Steps Performed
* Unit and feature tests are ✅
Move scroll marks to `TextBuffer`, so they can be cleared by
EraseInDisplay and EraseScrollback.
Also removes the namespacing on them.
## References and Relevant Issues
* see also #11000 and #15057
* Resize/Reflow _doesn't_ work yet and I'm not attempting this here.
## Validation Steps Performed
* `cls` works
* `Clear-Host` works
* `clear` works
* the "Clear buffer" action works
* They work when there's marks above the current viewport, and clear the
scrollback
* they work if you clear multiple "pages" of output, then scroll back to
where marks previously were
* resizing doesn't totally destroy the marks
Closes#15426
`ControlCore` contained two bugs:
* Race condition on access of the 3 throttled funcs which may now
be `reset()` during tear out
* The `ScrollPositionChanged` event emitter was written incorrectly
and would emit the event from the background thread without
throttling during tear out
## Summary of the Pull Request
This was a fever dream I had last July. What if, instead of `WINRT_PROPERTY` magic macros everywhere, we had actual templated versions you could debug into.
So instead of
```c++
WINRT_PROPERTY(bool, Deleted, false);
WINRT_PROPERTY(OriginTag, Origin, OriginTag::None);
WINRT_PROPERTY(guid, Updates);
```
you'd do
```c++
til::property<bool> Deleted{ false };
til::property<OriginTag> Origin{ OriginTag::None };
til::property<guid> Updates;
```
.... and then I just kinda kept doing that. So I did that for `til::event`.
**AND THEN LAST WEEK**
Raymond Chen was like: ["this is a good idea"](https://devblogs.microsoft.com/oldnewthing/20230317-00/?p=107946)
So here it is.
## Validation Steps Performed
Added some simple tests.
Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
A different take on #14548.
> We didn't love that a connection could transition back in the state
diagram, from Closed -> Start. That felt wrong. To remedy this, we're
going to allow the ControlCore to...
ASK the app to restart its connection. This is a much more sensible
approach, than leaving the ConnectionInfo in the core and having the
core do the restart itself. That's mental.
Cleanup from #14060Closes#14327
Obsoletes #14548
This bug causes AtlasEngine to render buffer contents with an incorrect
`cellCount`, which may either cause it to draw the contents only
partially, or potentially access the TextBuffer contents out of bounds.
`EnablePainting` sets the `_viewport` to the current viewport for some
unfortunate (and quite buggy/incorrect) caching purposes, which causes
`_CheckViewportAndScroll()` to think that the viewport hasn't changed
in the new window. We can ensure `_CheckViewportAndScroll()` works
by also setting `_forceUpdateViewport` to `true`.
Part of #14957
## PR Checklist
* Tear out a tab from a smaller window to a larger window
* Renderer contents adept to the larger window size ✅
---------
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
Adds a "Select command" and a "Select output" entry to the right-click
context menu when the user has shell integration enabled. This lets the
user quickly right-click on a command and select the entire commandline
or all of its output.
This was a "I'm waiting for reviews" sorta idea. Seemed like a
reasonable combination of features. Related to #13445, #11000.
Tested manually.
---------
Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
Adds two new commands, `selectOutput` and `selectCommand`. These don't
do much without shell integration enabled, unfortunately. If you do
enable it, however, you can use these commands to quickly navigate the
history to select whole commands (or their output).
Some sample JSON:
```json
{ "keys": "ctrl+shift+<", "command": { "action": "selectCommand", "direction": "prev" } },
{ "keys": "ctrl+shift+>", "command": { "action": "selectCommand", "direction": "next" } },
{ "keys": "ctrl+shift+[", "command": { "action": "selectOutput", "direction": "prev" } },
{ "keys": "ctrl+shift+]", "command": { "action": "selectOutput", "direction": "next" } },
```
**Demo gifs** in
https://github.com/microsoft/terminal/issues/4588#issuecomment-1352042789closes#4588
Tested manually.
<details>
<summary>CMD.exe user? It's dangerous to go alone! Take this.</summary>
Surely, there's a simpler way to do it, this is adapted from my own
script.
```cmd
prompt $e]133;D$e\$e]133;A$e\$e\$e]9;9;$P$e\$e[30;107m[$T]$e[97;46m$g$P$e[36;49m$g$e[0m$e[K$_$e[0m$e[94m%username%$e[0m@$e[32m%computername%$e[0m$G$e]133;B$e\
```
</details>
I noticed this bug while resizing my window on my 150% scale display.
Every 3 "snaps" of the window size, it would fail to resize the text
buffer. I found that this occurs, because we convert the swap chain
size from a float into a double, which converts my 597.333313 height
into 597.33331298828125, which then multiplied by 1.5 results in
895.999969482421875. If you just cast this to an integer, it'll
result in a height of 895px instead of the expected 896px.
This PR addresses the issue in two ways:
* Replace casts to integers with `lrint` or `floor`, etc.
* Remove many of the redundant double <> float conversions.
## PR Checklist
* Resizing my window always resizes the text buffer ✅
Rendering hyperlinks is unneccessarily complex at the moment, because
it requires you to implement `UpdateDrawingBrushes`, manually extract
the hyperlink flag from the given `TextAttribute` and save it until the
next call to `PaintBufferGridLines` which does not get that flag.
This isn't particularly clean as it assumes that `PaintBufferGridLines`
will be called after `UpdateDrawingBrushes` in the first place.
Instead, we can simply pass the hyperlink flag to `UpdateDrawingBrushes`
so that the renderers don't need to deal with this anymore.
## PR Checklist
* Hyperlinks show up with a dotted line ✅
* Hovering hyperlinks underline them ✅
_Lo! Harken to me, for I shall divulge the heart of the tab tear-out
saga. Verily, this PR shall bestow upon thee the power to move tabs and
panes between windows by means of pre-defined actions. Though be warned,
it does not yet grant thee the power to drag and drop them as thou
mayest desire. Yet, the same plumbing that underpins this work shall
remain steadfast. Behold, the majority of this undertaking concerns the
elevation of the RequestMoveContent event from the TerminalPage to the
very summit of the Monarch. From thence, a great AttachContent method
shall descend back to the lowest depths. Furthermore, there are minor
revisions to TermControl that shall enable thee to better detach the
content and attach it to a new one._
This is the most important part of the tab tear-out saga. This PR
enables the user to move tabs and panes between windows using
pre-defined actions. It does _not_ enable the user to drag/drop them
yet, but the same fundamental plumbing will still apply. Most of the PR
is plumbing the `RequestMoveContent` event up from the `TerminalPage` up
to the `Monarch`, and then plumbing an `AttachContent` method back down.
There are also small changes to `TermControl` to better support
detaching the content and attaching to a new one.
For testing, I recommend:
```json
{ "keys": "f1", "command": { "action": "moveTab", "window": "1" } },
{ "keys": "f2", "command": { "action": "moveTab", "window": "2" } },
{ "keys": "f3", "command": { "action": "movePane", "window": "1" } },
{ "keys": "f4", "command": { "action": "movePane", "window": "2" } },
{ "keys": "shift+f3", "command": { "action": "movePane", "window": "1", "index": 3 } },
{ "keys": "shift+f4", "command": { "action": "movePane", "window": "2", "index": 3 } },
```
* Related to #1256
* Related to #5000
---------
Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
This regression is caused by 0eff8c0. It previously said `.Y` here.
I went through the diff again and found no other width/height mistake.
Closes#14762Closes#15043
## Summary of the Pull Request
PR adds functionality to enable or disable readOnly mode within panes.
This functionality is different to toggling as if you call the same
functionality twice, it will not toggle between states.
## References and Relevant Issues
- Closes https://github.com/microsoft/terminal/issues/14415
- Documentation https://github.com/MicrosoftDocs/terminal/pull/645
## Validation Steps Performed
- Checked readOnly is enabled when command triggered
- Checked readOnly is enabled when command triggered while read only
already enabled
- Checked readOnly is disabled when command triggered while read only is
enabled
- Checked readOnly stays disabled when command triggered while read only
is disabled
- Checked above with multiple tabs and split panes
## PR Checklist
- [ ] Closes#14415
- [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/645
- [X] Schema updated (if necessary)
---------
Co-authored-by: Mike Griese <migrie@microsoft.com>
Interesting things we could do after this:
- remove all `InitializeComponent` calls - they do it automatically
- have some Clang support (!)
- use `std::optional`<->`IReference` automatic binding
- use `std::format` support (!) for json/uri/hostname/http stuff/all
`IStringable`s
- potentially move to `/await:strict` for C++20 coroutines
I've also fixed up a couple ambiguities introduced by this change.
Does what it says in the title. After this commit you can customize the height
and width of the terminal's cells. This commit supports parts of CSS'
`<length-percentage>` data type: Font-size relative sizes as multiples (`1.2`),
percentage (`120%`), or advance-width relative (`1.2ch`), as well as absolute
sizes in CSS pixels (`px`) or points (`pt`).
This PR is neither bug free in DxEngine, nor in AtlasEngine.
The former fails to implement glyph advance corrections (for instance #9381),
as well as disallowing glyphs to overlap rows. The latter has the same
overlap issue, but more severely as it tries to shrink glyphs to fit in.
Closes#3498Closes#14068
## Validation Steps Performed
* Setting `height` to `1` creates 12pt tall rows ✅
* Setting `height` to `1ch` creates square cells ✅
* Setting `width` to `1` creates square cells ✅
* Setting `width` or `height` to `Npx` or `Npt` works ✅
* Trailing zeroes are trimmed properly during serialization ✅
* Patching the PR to allow >100 line heights and entering "100.123456"
displays 6 fractional digits ✅
`s/it's/its/`
Note that I didn't touch the several errors in the doc and doc/spec directories, since those seem to be dated and signed email excerpts, and I don't want to violate authorial integrity. Let me know if you would like me to fix those as well.
## References
p 57. Murray, L. (1824). English grammar. Philadelphia : E. T. Scott.
I skimmed several hundred usages of the word "it's" in the code. This actually wasn't as tiresome as it sounds, since many of the code comments in this repo are entertaining and educational — the adjectives do not _necessarily_ apply in that order, but do _possibly_ apply in that order.
When a terminal process exits (successful or not) and the profile isn't
set to automatically close the pane, a new message is displayed:
You can now close this terminal with ^D or Enter to restart.
Ctrl+D then is able to close the pane and Enter restarts it.
I originally tried to do this at the ConptyConnection layer by changing
the connection state from Failed to Closed, but then that didn't work
for the case where the process exited successfully but the profile isn't
set to exit automatically. So, I added an event to
ControlCore/TermControl that Pane watches. ControlCore watches to see if
the input is Ctrl+D (0x4) and if the connection is closed or failed, and
then raises the event so that Pane can close itself. As it turned out, I
think this is the better place to have the logic to watch for the Ctrl+D
key. Doing it at the ConptyConnection layer meant I had to parse out the
key from the escaped text passed to ConptyConnection::WriteInput.
## Validation Steps Performed
Tried adding lots of panes and then killing the processes outside of
Terminal. Each showed the new message and I could close them with Ctrl+D
or restart them with Enter. Also set a profile to never close
automatically to make sure Ctrl+D would work when a process exits
successfully.
Closes#12849Closes#11570Closes#4379
My goal is to make `IRenderData` "snapshottable", so that we
can render a frame of text without holding the console lock.
To facilitate this, this commit merges our 3 data interfaces
into one. It includes no actual changes apart from renames.
My long-term plan is to replace the `CodepointWidth` enum with a simple integer
return value that indicates the amount of columns a codepoint is wide.
This is necessary so that we can return 0 for ZWJs (zero width joiners).
This initial commit represents a cleanup effort around `CodepointWidthDetector`.
Since less code runs faster, this change has the nice side-effect of running
roughly 5-10% faster across the board. It also drops the binary size by ~1.2kB.
## Validation Steps Performed
* `CodepointWidthDetectorTests` passes ✅
* U+26bf (``"`u{26bf}"`` inside pwsh) is a wide glyph
in OpenConsole and narrow one in Windows Terminal ✅
As noted in #11000.
This adds support for `FTCS_COMMAND_START`, `FTCS_COMMAND_EXECUTED` and `FTCS_COMMAND_FINISHED`, which allow a shell to more clearly markup parts of the buffer.
As a trick, I'm also making the `experimental.autoMarkPrompts` setting act like a `FTCS_COMMAND_EXECUTED` if it comes after a `FTCS_COMMAND_START`. This lets the whole sequence work for cmd.exe (which wouldn't otherwise be possible).
* My cmd prompt
```bat
PROMPT $e]133;D$e\$e]133;A$e\$e]9;9;$P$e\[$T]$e[97;46m%_seperator%$P$e[36;49m%_seperator%$e[0m$_$e[0m%_GITPROMPT%$e[94m%username%$e[0m@$e[32m%computername%$e[0m$G$e]133;B$e\
```
* pwsh profile, heavily cribbed from vscode
```pwsh
$Global:__LastHistoryId = -1
function Global:__Terminal-Get-LastExitCode {
if ($? -eq $True) {
return 0
}
# TODO: Should we just return a string instead?
# return -1
if ("$LastExitCode" -ne "") { return $LastExitCode }
return -1
}
function prompt {
# $gle = $LastExitCode
$gle = $(__Terminal-Get-LastExitCode);
$LastHistoryEntry = $(Get-History -Count 1)
# Skip finishing the command if the first command has not yet started
if ($Global:__LastHistoryId -ne -1) {
if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
# Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command)
$out += "`e]133;D`a"
} else {
# Command finished exit code
# OSC 633 ; D [; <ExitCode>] ST
$out += "`e]133;D;$gle`a"
}
}
$loc = $($executionContext.SessionState.Path.CurrentLocation);
# IMPORTANT: Make sure there's a printable charater _last_ in the prompt.
# Otherwise, PSReadline is gonna use the terminating `\` here and colorize
# that if it detects a syntax error
$out += "`e]133;A$([char]07)";
$out += "`e]9;9;`"$loc`"$([char]07)";
$out += "PWSH $loc$('>' * ($nestedPromptLevel + 1)) ";
$out += "`e]133;B$([char]07)";
$Global:__LastHistoryId = $LastHistoryEntry.Id
return $out
}
```
* Doesn't close any issues, because this was always just an element in #11000
* I work here
* From FHL code
This is a follow-up of #13025 to make the members of `til::point/size/rect`
uniform and consistent without the use of `unions`. The only file that has
any changes is `src/host/getset.cpp` where an if condition was simplified.
## Validation Steps Performed
* Host unit tests ✅
* Host feature tests ✅
* ControlCore feature tests ✅
This commit replaces `Utf16Parser` with `<til/unicode.h>` which includes:
* `til::utf16_iterator` as a replacement for `Utf16Parser::Parse`
* `til::utf16_next` as a replacement for `Utf16Parser::ParseNext`
This fixes 2 bugs with `Utf16Parser`:
* Swallowing invalid surrogate pairs instead of turning them into U+FFFD.
* `std::vector<std::vector<wchar_t>>`. It's now >12000% faster.
## Validation Steps Performed
* New unit tests pass ✅
* Searching for narrow/wide characters in conhost works ✅
## Summary of the Pull Request
Ensures that reading the buffer content actually returns the content.
## References
Regressed in #13626.
## PR Checklist
* [x] Closes#14378
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
## Validation Steps Performed
Added a test.
This commit is a from-scratch rewrite of `ROW` with the primary goal to get
rid of the rather bodgy `UnicodeStorage` class and improve Unicode support.
Previously a 120x9001 terminal buffer would store a vector of 9001 `ROW`s
where each `ROW` stored exactly 120 `wchar_t`. Glyphs exceeding their
allocated space would be stored in the `UnicodeStorage` which was basically
a `hashmap<Coordinate, String>`. Iterating over the text in a `ROW` would
require us to check each glyph and fetch it from the map conditionally.
On newlines we'd have to invalidate all map entries that are now gone,
so for every invalidated `ROW` we'd iterate through all glyphs again and if
a single one was stored in `UnicodeStorage`, we'd then iterate through the
entire hashmap to remove all coordinates that were residing on that `ROW`.
All in all, this wasn't the most robust nor performant code.
The new implementation is simple (from a design perspective):
Store all text in a `ROW` in a regular string. Grow the string if needed.
The association between columns and text works by storing character offsets
in a column-wide array. This algorithm is <100 LOC and removes ~1000.
As an aside this PR does a few more things that go hand in hand:
* Remove most of `ROW` helper classes, which aren't needed anymore.
* Allocate backing memory in a single `VirtualAlloc` call.
* Rewrite `IsCursorDoubleWidth` to use `DbcsAttrAt` directly.
Improves overall performance by 10-20% and makes this implementation
faster than the previous NxM storage, despite the added complexity.
Part of #8000
## Validation Steps Performed
* Existing and new unit and feature tests complete ✅
* Printing Unicode completes without crashing ✅
* Resizing works without crashing ✅
My hope with this commit is to make our code more robust against accidental
recursive locking, as well as making it easier to write code with confidence,
with only a slight performance trade-off.
## Validation Steps Performed
* Playing Pac Man music through MIDI ✅
* Windows Terminal runs happily ever after ✅
Silent MIDI notes can be used to seemingly deny a user's input for long
durations (multiple minutes). This commit improves the situation by ignoring
all DECPS sequences for a second when Ctrl+C/Ctrl+Break is pressed.
Additionally it fixes a regression introduced in 666c446:
When we close a tab we need to unblock/shutdown `MidiAudio` early,
so that `ConptyConnection::Close()` can run down as fast as possible.
## Validation Steps Performed
* In pwsh in Windows Terminal 1.16 run ``while ($True) { echo "`e[3;8;3,~" }``
* Ctrl+C doesn't do anything ❎
* Closing the tab doesn't do anything ❎
* With these modifications in Windows Terminal:
* Ctrl+C stops the output ✅
* Closing the tab completes instantly ✅
* With these modifications in OpenConsole:
* Ctrl+C stops the output ✅
* Closing the window completes instantly ✅