This is in advance of the UWP/WPF unification work, where we need to
provide a dispatcher from the WPF side. It should be a no-op today, but
we could also move _loading_ the dispatcher up to TermControl (XAML).
These do not notionally belong to TermControl; as we prepare to move
automatic scrolling down into the Interactivity layer (for
WpfTerminalControl to depend on,) we're going to need to track these in
Interactivity too.
This also converts the arguments from the winrt-specific
Foundation::Point to Core::Point, and simplifies some of the signatures
to no longer pass things we do not need to pass.
Right now, this should result in no functional change.
Some of the tests are sensitive to the fact that we never loaded
`SPI_GETWHEELSCROLLLINES`
and instead defaulted to `1`. They've been hammered back into shape.
This adds support for horizontal mouse wheel events (`WM_MOUSEHWHEEL`).
With this change, applications running in the terminal can now receive
and respond to horizontal scroll inputs from the mouse/trackpad.
Closes#19245Closes#10329
For some reason, we went real hard on an architecture where the settings
object contained the key bindings handler for the terminal. To make this
work, we had to wind it through tons of layers: `TermControl`,
`ControlInteractivity`, `ControlCore` (which saved it on
`ControlSettings`), `ControlSettings`. Of course, because we have no
clear delineation of concerns at the App layer this required us to put
the bindings into the Settings Cache[^1].
Well, `TermControl` used `ControlCore` to get the Settings, to get the
Bindings, to dispatch keys.
Yes, `TermControl` stored `IKeyBindings` down three layers _only to fish
it back out and use it itself._
There is one place in the application where `TermControl`s are hooked up
to their owners. Instead of passing the key bindings dispatcher in
through nine hundred layers, we can just set it once--definitively!--
there.
[^1]: This was the last thing that made the settings cache
page-specific...
* Moves clipboard writing from `ControlCore` to `TerminalPage`.
This requires adding a bunch of event types and logic.
This is technically not needed anymore after changing the
direction of this PR, but I kept it because it's better.
* Add a `WarnAboutMultiLinePaste` enum to differentiate between
"paste without warning always/never/if-bracketed-paste-disabled".
Closes#13014
Closes https://github.com/microsoft/edit/issues/279
## Validation Steps Performed
* Launch Microsoft Edit and copy text with a trailing newline
* Paste it with Ctrl+Shift+V
* It's pasted as it was copied ✅
* Changing the setting to "always" always warns ✅
Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
- Various spelling fixes
- Refresh metadata (including dictionaries)
- Upgrade to v0.0.25
## Validation Steps Performed
- check-spelling has been automatically testing this repository for a
while now on a daily basis to ensure that it works fairly reliably:
https://github.com/check-spelling-sandbox/autotest-check-spelling/actions/workflows/microsoft-terminal-spelling2.yml
Specific in-code fixes:
- winget
- whereas
- tl;dr
- set up
- otherwise,
- more,
- macbook
- its
- invalid
- in order to
- if
- if the
- for this tab,...
- fall back
- course,
- cch
- aspect
- archaeologists
- an
- all at once
- a
- `...`
- ; otherwise,
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Selection is generally stored as an inclusive start and end. This PR
makes the end exclusive which now allows degenerate selections, namely
in mark mode. This also modifies mouse selection to round to the nearest
cell boundary (see #5099) and improves word boundaries to be a bit more
modern and make sense for degenerate selections (similar to #15787).
Closes#5099Closes#13447Closes#17892
## Detailed Description of the Pull Request / Additional comments
- Buffer, Viewport, and Point
- Introduced a few new functions here to find word boundaries, delimiter
class runs, and glyph boundaries.
- 📝These new functions should be able to replace a few other functions
(i.e. `GetWordStart` --> `GetWordStart2`). That migration is going to be
a part of #4423 to reduce the risk of breaking UIA.
- Viewport: added a few functions to handle navigating the _exclusive_
bounds (namely allowing RightExclusive as a position for buffer
coordinates). This is important for selection to be able to highlight
the entire line.
- 📝`BottomInclusiveRightExclusive()` will replace `EndExclusive` in the
UIA code
- Point: `iterate_rows_exclusive` is similar to `iterate_rows`, except
it has handling for RightExclusive
- Renderer
- Use `iterate_rows_exclusive` for proper handling (this actually fixed
a lot of our issues)
- Remove some workarounds in `_drawHighlighted` (this is a boundary
where we got inclusive coords and made them exclusive, but now we don't
need that!)
- Terminal
- fix selection marker rendering
- `_ConvertToBufferCell()`: add a param to allow for RightExclusive or
clamp it to RightInclusive (original behavior). Both are useful!
- Use new `GetWordStart2` and `GetWordEnd2` to improve word boundaries
and make them feel right now that the selection an exclusive range.
- Convert a few `IsInBounds` --> `IsInExclusiveBounds` for safety and
correctness
- Add `TriggerSelection` to `SelectNewRegion`
- 📝 We normally called `TriggerSelection` in a different layer, but it
turns out, UIA's `Select` function wouldn't actually update the
renderer. Whoops! This fixes that.
- TermControl
- `_getTerminalPosition` now has a new param to round to the nearest
cell (see #5099)
- UIA
- `TermControlUIAProvider::GetSelectionRange` no need to convert from
inclusive range to exclusive range anymore!
- `TextBuffer::GetPlainText` now works on an exclusive range, so no need
to convert the range anymore!
## Validation Steps Performed
This fundamental change impacts a lot of scenarios:
- ✅Rendering selections
- ✅Selection markers
- ✅Copy text
- ✅Session restore
- ✅Mark mode navigation (i.e. character, word, line, buffer)
- ✅Mouse selection (i.e. click+drag, shift+click, multi-click,
alt+click)
- ✅Hyperlinks (interaction and rendering)
- ✅Accessibility (i.e. get selection, movement, text extraction,
selecting text)
- [ ] Prev/Next Command/Output (untested)
- ✅Unit tests
## Follow-ups
- Refs #4423
- Now that selection and UIA are both exclusive ranges, it should be a
lot easier to deduplicate code between selection and UIA. We should be
able to remove `EndExclusive` as well when we do that. This'll also be
an opportunity to modernize that code and use more `til` classes.
## Summary of the Pull Request
This extends the copy command to be able to include control sequences,
for use in tools that subsequently know how to parse and display that.
## References and Relevant Issues
https://github.com/microsoft/terminal/issues/15703
## Detailed Description of the Pull Request / Additional comments
At a high level, this:
- Expands the `CopyTextArgs` to have a `withControlSequences` bool.
- Plumbs that bool down through many layers to where we actuall get
data out of the text buffer.
- Modifies the existing `TextBuffer::Serialize` to be more generic
and renames it to `TextBuffer::ChunkedSerialize`.
- Uses the new `ChunkedSerialize` to generate the data for the copy
request.
## Validation Steps Performed
To test this I've manually:
- Generated some styled terminal contents, copied it with the control
sequences, pasted it into a file, `cat`ed the file and seen that it
looks the same.
- Set `"firstWindowPreference": "persistedWindowLayout"` and
validated that the contents of windows are saved and
restored with styling intact.
I also checked that `Invoke-OpenConsoleTests` passed.
## PR Checklist
- [x] Closes#15703
- [ ] 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/756
- [x] Schema updated (if necessary)
`pathTranslationStyle` has four options:
- `none`: Do no translation
- `wsl`: Translate `C:\` to `/mnt/c` and `\\wsl$\Foo\bar` to `/bar`
- `cygwin`: Translate `C:\` to `/cygdrive/c`
- `msys2`: Translate `C:\` to `/c`
It is intended as a broadly-supported replacement for us checking the
source every time the user drops a path.
We no longer need to push the source name all the way down to the
control.
I am hesitant to commit to using other folks' product names in our
settings model,
however, these are almost certainly more recognizable than whatever
other weird
names we could come up with.
The Git Bash fragment extension profile could conceivably use
`pathTranslationStyle`
`msys2` to make sure drag/dropped paths look right.
I sure hope I didn't break anything!
While `til::math` was a good idea its convenience led us to use it
in the only place where it absolutely must not be used: The UI code.
So, this PR replaces all those `til::point`s, etc., with floats.
Now we use DIPs consistently throughout all layers of the UI code,
except for the UIA area (that would've required too many changes).
## Validation Steps Performed
Launch, looks good, no obvious defects, UIA positioning seems ok. ✅
PR #10642 and #11290 introduced an adjustment for the cursor position
used to generate VT mouse mode events.
One of the decisions made in those PRs was to only send coordinates
where Y was >= 0, so if you were off the top of the screen you wouldn't
get any events. However, terminal emulators are expected to send
_clamped_ events when the mouse is off the screen. This decision broke
clamping Y to 0 when the mouse was above the screen.
The other decision was to only adjust the Y coordinate if the core's
`ScrollOffset` was greater than 0. It turns out that `ScrollOffset` _is
0_ when you are scrolled all the way back in teh buffer. With this
check, we would clamp coordinates properly _until the top line of the
scrollback was visible_, at which point we would send those coordinates
over directly. This resulted in the same weird behavior as observed in
#10190.
I've fixed both of those things. Core is expected to receive negative
coordinates and clamp them to the viewport. ScrollOffset should never be
below 0, as it refers to the top visible buffer line.
In addition to that, #17744 uncovered that we were allowing
autoscrolling to happen even when VT mouse events were being generated.
I added a way for `ControlInteractivity` to halt further event
processing. It's crude.
Refs #10190Closes#17744
While `double` is probably generally preferable for UI code,
our application is essentially a complex wrapper wrapper around
DWrite, D2D and D3D, all of which use `float` exclusively.
Of course it also uses XAML, but that one uses `float` for roughly
1/3rd of its API functions, so I'm not sure what it prefers.
Additionally, it's mostly a coincidence that we use WinUI/XAML for
Windows Terminal whereas DWrite/D2D/D3D are effectively essential.
This is demonstrated by the fact that we have a `HwndTerminal`,
while there's no alternative to e.g. D3D on Windows.
The goal of this PR is that DIP based calculations never end up
mixing `float` and `double`. This PR also changes opacity-related
values to `float` because I felt like that fits the theme.
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>
The Win32 API is significantly faster than the WinRT one, in the order
of around 300-1000x depending on the CPU and CPU load.
This might slightly improve the situation around #15315, but I suspect
that it requires many more fixes. For instance, we don't really have a
single text input "queue" into which we write. Multiple routines that
`resume_background` just to `WriteFile` into the input pipe are thus
racing against each other, contributing to the laggy feeling.
I also fear that the modern Windows text stack might be inherently
RPC based too, producing worse lag with rising CPU load.
This might fix#14323
## Validation Steps Performed
* Paste text from Edge ✅
* Paste text from Notepad ✅
* Right click the address bar in Explorer, choose "Copy address",
paste text into WT ✅
---------
Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
_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>
## Summary
_Thus we come to the introduction of a new servant, the
`ContentManager`, a singular entity that serves at the behest of the
`emperor`. It is its charge to keep track of all `TermControl` instances
created by the windows, for each window must seek its blessing before
calling forth such an instance._
_With the aid of the `ContentManager`, the `TermControl` shall now be
traced by the hand of fate through the use of unique identifying marks,
known as `GUID`s. Yet, its purpose remains yet unknown, for it is merely
a waypoint upon the journey yet to come._
_This act of bridging also brings a change to the handling of events
within the `TermControl`. This change shall see the addition of a
revoker, similar to the manner in which the `AppHost` hath employed it,
to the `TermControl`. Additionally, there is a new layer of indirection
between the `ControlCore` and the `App` layer, making ready for the day
when the `TermControl` may be repositioned and re-parented with ease._
_Consider this but a trivial act, a mere shadow of things yet to come,
for its impact shall be felt but briefly, like the passing of a gentle
breeze._
Related to #5000
Related to #1256
# Detailed description
This PR is another small bridge PR between the big work in #14843, and
the PR that will enable panes to move between windows.
This introduces a new class, called `ContentManager`. This is a global
singleton object, owned by the emperor. Whenever a window wants to
instantiate a new `TermControl`, it must ask the ContentManager to give
it one. This allows the ContentManager to track each "content" by GUID.
That's it. We don't do anything with them in this PR by itself, we just
track them.
This also includes a small change to the way TermControl events are
handled. It adds an `AppHost`-like revoker struct, and weak_ref's all
the handlers. We also add a layer of indirection between the
ControlCore's raising of events and the App layer's handling. This will
make reparenting content easier in the future.
This is a pretty trivial change which shouldn't have any major side
effects. Consider it exposition of the things to come. It's
intentionally small to try and keep the reviews more managable.
Experimental for now. `experimental.rightClickContextMenu`, a
per-profile setting. Long term we want to enable full mouse bindings, at
which point this would be replaced.
Closes#3337
This adds **two** context menus to the `TermControl` - one for
right-clicking with a selection, and one without. The implementation is
designed to follows the API experience of the context menu on something
like a [`RichEditBox`](winui2gallery://item/RichEditBox). The hosting
application adds a handler for the menu's `Opening` event, and appends
whatever items it wants at that time.
So `TermControl` only implements a few "actions" by default - copy,
past, find. `TerminalApp` is then responsible for adding anything else
it needs. Right now, those actions are:
* Duplicate tab
* Duplicate pane
* Close Tab
* Close pane
Screenshots in
https://github.com/microsoft/terminal/pull/14775#issuecomment-1415737393
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.
After this commit a user may specify fractional font sizes.
Support was only implemented for AtlasEngine however.
DxEngine continues to use rounded (integer) font sizes.
Closes#6678
## Validation Steps Performed
* Install a bitmap font that requires fractional font sizes
(e.g. Terminus TTF, https://files.ax86.net/terminus-ttf/)
* Set font size to something integer (e.g. 14pt)
Glyphs are blurry ✅
* Set font size to something fractional (e.g. 13.5pt)
Glyphs are crisp ✅
Previously this project used a great variety of types to present text buffer
coordinates: `short`, `unsigned short`, `int`, `unsigned int`, `size_t`,
`ptrdiff_t`, `COORD`/`SMALL_RECT` (aka `short`), and more.
This massive commit migrates almost all use of those types over to the
centralized types `til::point`/`size`/`rect`/`inclusive_rect` and their
underlying type `til::CoordType` (aka `int32_t`).
Due to the size of the changeset and statistics I expect it to contain bugs.
The biggest risk I see is that some code potentially, maybe implicitly, expected
arithmetic to be mod 2^16 and that this code now allows it to be mod 2^32.
Any narrowing into `short` later on would then throw exceptions.
## PR Checklist
* [x] Closes#4015
* [x] I work here
* [x] Tests added/passed
## Validation Steps Performed
Casual usage of OpenConsole and Windows Terminal. ✅
"Alternate scroll mode" is a neat little mode where the app wants mouse wheel events to come through as arrow keypresses instead, when in the alternate buffer. Now that we've got support for the alt buffer in the Terminal, we can support this as well.
* [x] Closes https://github.com/microsoft/terminal/issues/3321
* [x] I work here
* [ ] Tests would be nice
Tested manually with
```bash
printf "\e[?1007h" ; man ps
```
This commit makes the following changes to `til::point/size/rectangle`
for the following reasons:
* Rename `rectangle` into `rect`
This will make the naming consistent with a later `small_rect` struct
as well as the existing Win32 POINT/SIZE/RECT structs.
* Standardizes til wrappers on `int32_t` instead of `ptrdiff_t`
Provides a consistent behavior between x86 and x64, preventing accidental
errors on x86, as it's less rigorously tested than x64. Additionally it
improves interop with MIDL3 which only supports fixed width integer types.
* Standardizes til wrappers on throwing `gsl::narrow_error`
Makes the behavior of our code more consistent.
* Makes all eligible functions `constexpr`
Because why not.
* Removes implicit constructors and conversion operators
This is a complex and controversial topic. My reasons are: You can't Ctrl+F
for an implicit conversion. This breaks most non-IDE engines, like the one on
GitHub or those we have internally at MS. This is important for me as these
implicit conversion operators aren't cost free. Narrowing integers itself,
as well as the boundary checks that need to be done have a certain,
fixed overhead each time. Additionally the lack of noexcept prevents
many advanced compiler optimizations. Removing their use entirely
drops conhost's code segment size by around ~6.5%.
## References
Preliminary work for #4015.
## PR Checklist
* [x] I work here
* [x] Tests added/passed
## Validation Steps Performed
I'm mostly relying on our unit tests here. Both OpenConsole and WT appear to work fine.
* Adds the `cppwinrt_utils.h` to the include path for all winrt projects.
* Adds the file to the pch.h's for every project, so you don't need to include it in every header.
* Replaces some usages of `DECLARE_EVENT`/`DEFINE_EVENT` with `WINRT_CALLBACK`, which will do it in a oneliner
* Adds more `BASIC_FACTORY` usage.
* [x] Closes#2456
It's 92 files with one-line changes, don't be scared.
## Summary of the Pull Request
Currently, the TermControl and ControlCore recieve a settings object that implements `IControlSettings`. They use for this for both reading the settings they should use, and also storing some runtime overrides to those settings (namely, `Opacity`). The object they recieve currently is a `T.S.M.TerminalSettings` object, as well as another `TerminalSettings` object if the user wants to have an `unfocusedAppearance`. All these are all hosted in the same process, so everything is fine and dandy.
With the upcoming move to having the Terminal split into multiple processes, this will no longer work. If the `ControlCore` in the Content Process is given a pointer to a `TerminalSettings` in a certain Window Process, and that control is subsequently moved to another window, then there's no guarantee that the original `TerminalSettings` object continues to exist. In this scenario, when window 1 is closed, now the Core is unable to read any settings, because the process that owned that object no longer exists.
The solution to this issue is to have the `ControlCore`'s own their own copy of the settings they were created with. that way, they can be confident those settings will always exist. Enter `ControlSettings`, a dumb struct for just storing all the contents of the Settings. I used x-macros for this, so that we don't need to copy-paste into this file every time we add a setting.
Changing this has all sorts of other fallout effects:
* Previewing a scheme/anything is a tad bit more annoying. Before, we could just sneak the previewed scheme into a `TerminalSettings` that lived between the settings we created the control with, and the settings they were actually using, and it would _just work_. Even explaining that here, it sounds like magic, because it was. However, now, the TermControl can't use a layered `TerminalSettings` for the settings anymore. Now we need to actually read out the current color table, and set the whole scheme when we change it. So now there's also a `Microsoft.Terminal.Core.Scheme` _struct_ for holding that data.
- Why a `struct`? Because that will go across the process boundary as a blob, rather than as a pointer to an object in the other process. That way we can transit the whole struct from window to core safely.
* A TermControl doesn't have a `IControlSettings` at all anymore - it initalizes itself via the settings in the `Core`. This will be useful for tear-out, when we need to have the `TermControl` initialize itself from just a `ControlCore`, without being able to rebuild the settings from scratch.
* The `TabTests` that were written under the assumption that the Control had a layered `TerminalSettings` obviously broke, as they were designed to. They've been modified to reflect the new reality.
* When we initialize the Control, we give it the settings and the `UnfocusedAppearance` all at once. If we don't give it an `unfocusedAppearance`, it will just use the focused appearance as the unfocused appearance.
* The Control no longer can _write_ settings to the `ControlSettings`. We don't want to be storing things in there. Pretty much everything we set in the control, we store somewhere other than in the settings object itself. However, `opacity` and `useAcrylic`, we need to store in a handy new `RUNTIME_SETTING` property. We can write those runtime overrides to those properties.
* We no longer store the color scheme for a pane in the persisted state. I'm tracking that in #9800. I don't think it's too hard to add back, but I wanted this in front of eyes sooner than later.
## References
* #1256
* #5000
* #9794 has the scheme previewing in it.
* #9818 is WAY more possible now.
## PR Checklist
* [x] Surprisingly there wasn't ever a card or issue for this one. This was only ever a bullet point in #5000.
* A bunch of these issues were fixed along the way, though I never intended to fix them:
* [x] Closes#11571
* [x] Closes#11586
* [x] Closes#7219
* [x] Closes#11067
* [x] I think #11623 actually ended up resolving this one, but I'm double tapping on it here: Closes#5703
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated
## Detailed Description of the Pull Request / Additional comments
Along the way I tried to clean up code where possible, but not too agressively.
I didn't end up converting the various `MockTerminalSettings` classes used in tests to the x macros quite yet. I wanted to merge this with #11416 in `main` before I went too crazy.
## Validation Steps Performed
* [x] Scheme previewing works
* [x] Adjusting the font size works
* [x] focused/unfocused appearances still work
* [x] mouse-wheeling opacity still works
* [x] acrylic & cleartype still does the right thing
* [x] saving the settings still works
* [x] going wild on sliding the opacity slider in the settings doesn't crash the terminal
* [x] toggling retro effects with a keybinding still works
* [x] toggling retro effects with the command palette works
* [x] The matrix of (`useAcrylic(true,false)`)x(`opacity(50,100)`)x(`antialiasingMode(cleartype, grayscale)`) works as expected. Slightly changed, falls back to grayscale more often, but looks more right.
This commit introduces "AtlasEngine", a new text renderer based on DxEngine.
But unlike it, DirectWrite and Direct2D are only used to rasterize glyphs.
Blending and placing these glyphs into the target view is being done using
Direct3D and a simple HLSL shader. Since this new renderer more aggressively
assumes that the text is monospace, it simplifies the implementation:
The viewport is divided into cells, and its data is stored as a simple matrix.
Modifications to this matrix involve only simple pointer arithmetic and is easy
to understand. But just like with DxEngine however, DirectWrite
related code remains extremely complex and hard to understand.
Supported features:
* Basic text rendering with grayscale AA
* Foreground and background colors
* Emojis, including zero width joiners
* Underline, dotted underline, strikethrough
* Custom font axes and features
* Selections
* All cursor styles
* Full alpha support for all colors
* _Should_ work with Windows 7
Unsupported features:
* A more conservative GPU memory usage
The backing texture atlas for glyphs is grow-only and will not shrink.
After 256MB of memory is used up (~20k glyphs) text output
will be broken until the renderer is restarted.
* ClearType
* Remaining gridlines (left, right, top, bottom, double underline)
* Hyperlinks don't get full underlines if hovered in WT
* Softfonts
* Non-default line renditions
Performance:
* Runs at up to native display refresh rate
Unfortunately the frame rate often drops below refresh rate, due us
fighting over the buffer lock with other parts of the application.
* CPU consumption is up to halved compared to DxEngine
AtlasEngine is still highly unoptimized. Glyph hashing
consumes up to a third of the current CPU time.
* No regressions in WT performance
VT parsing and related buffer management takes up most of the CPU time (~85%),
due to which the AtlasEngine can't show any further improvements.
* ~2x improvement in raw text throughput in OpenConsole
compared to DxEngine running at 144 FPS
* ≥10x improvement in colored VT output in WT/OpenConsole
compared to DxEngine running at 144 FPS
## Summary of the Pull Request
This fixes two bugs related to dragging into the bounds of the `TermControl`. Although the fixes are fairly small, I'm batching them up, because I don't want to stack 2 more PRs on top of #10051.
* #9109
- This is fixed by only starting an autoscroll if the click&drag actually started within the bounds of the control.
* #4603
- Building on the above change, only modify the selection when the drag started in the control.
## References
* srsly go read #10051.
## PR Checklist
* [x] Closes#9109
* [x] Closes#4603
* [x] I work here
* [x] Test added
* [n/a] Requires documentation to be updated
## Detailed Description of the Pull Request / Additional comments
This is kind of annoying that the auto-scrolling is handled by the TermControl, but it uses a timer that's still a WinUI construct.
We only want to start the auto-scrolling behavior when the drag started _inside_ the control. Otherwise, in the tab drag scenario, dragging into the bounds of the TermControl will trick it into thinking it should start a scroll.
## 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.
## Summary of the Pull Request
This PR encompasses the first three bugs we found post-#9820.
### A: Mousedown, select, SCROLL does a weird thing with endpoints that doesn't happen in stable
We were using the terminal position to set the selection anchor, when we should have used the pixel position.
This is fixed in 4f4df01.
### B: Trackpad scrolling down with small increments seems buggy
This one's the most complicated. The touchpad sends very many small scroll deltas, less than one row at a time. The control scrollbar can store a `double`, so small deltas can accumulate. Originally, these would accumulate in the scrollbar, and we'd only read that out as an `int` in the scrollbar updater, which is throttled.
In the interactivity split, there's no place for us to store that double. We immediately narrow to an `int` for `ControlInteractivity::_updateScrollbar`.
So this introduces a double inside `ControlInteractivity` as a fake scrollbar, with which to accumulate to.
This is fixed in 33d29fa...0fefc5b
### C: Looks like there's a selection issue when you click and drag too quickly.
The diff for this one is:
<table>
<tr><td>1.8</td><td>main</td></tr>
<tr>
<td>
```c++
if (_singleClickTouchdownPos)
{
// Figure out if the user's moved a quarter of a cell's smaller axis away from the clickdown point
auto& touchdownPoint{ *_singleClickTouchdownPos };
auto distance{ std::sqrtf(std::powf(cursorPosition.X - touchdownPoint.X, 2) + std::powf(cursorPosition.Y - touchdownPoint.Y, 2)) };
const til::size fontSize{ _actualFont.GetSize() };
const auto fontSizeInDips = fontSize.scale(til::math::rounding, 1.0f / _renderEngine->GetScaling());
if (distance >= (std::min(fontSizeInDips.width(), fontSizeInDips.height()) / 4.f))
{
_terminal->SetSelectionAnchor(_GetTerminalPosition(touchdownPoint));
// stop tracking the touchdown point
_singleClickTouchdownPos = std::nullopt;
}
}
```
</td>
<td>
```c++
if (_singleClickTouchdownPos)
{
// Figure out if the user's moved a quarter of a cell's smaller axis away from the clickdown point
auto& touchdownPoint{ *_singleClickTouchdownPos };
float dx = ::base::saturated_cast<float>(pixelPosition.x() - touchdownPoint.x());
float dy = ::base::saturated_cast<float>(pixelPosition.y() - touchdownPoint.y());
auto distance{ std::sqrtf(std::powf(dx, 2) +
std::powf(dy, 2)) };
const auto fontSizeInDips{ _core->FontSizeInDips() };
if (distance >= (std::min(fontSizeInDips.width(), fontSizeInDips.height()) / 4.f))
{
_core->SetSelectionAnchor(terminalPosition);
// stop tracking the touchdown point
_singleClickTouchdownPos = std::nullopt;
}
}
```
</td>
</tr>
</table>
```c++
_terminal->SetSelectionAnchor(_GetTerminalPosition(touchdownPoint));
```
vs
```c++
_core->SetSelectionAnchor(terminalPosition);
```
We're now using the location of the drag event as the selection anchor, instead of the location that the user initially clicked. Oops.
## PR Checklist
* [x] Checks three boxes, though I'll be shocked if they're the last.
* [x] I work here
* [x] Tests added/passed 🎉🎉🎉
* [n/a] Requires documentation to be updated
## Detailed Description of the Pull Request / Additional comments
All three have tests, 🙌🙌🙌🙌
## Validation Steps Performed
Manual, and automated via tests
## Summary of the Pull Request
Brace yourselves, it's finally here. This PR does the dirty work of splitting the monolithic `TermControl` into three components. These components are:
* `ControlCore`: This encapsulates the `Terminal` instance, the `DxEngine` and `Renderer`, and the `Connection`. This is intended to everything that someone might need to stand up a terminal instance in a control, but without any regard for how the UX works.
* `ControlInteractivity`: This is a wrapper for the `ControlCore`, which holds the logic for things like double-click, right click copy/paste, selection, etc. This is intended to be a UI framework-independent abstraction. The methods this layer exposes can be called the same from both the WinUI TermControl and the WPF control.
* `TermControl`: This is the UWP control. It's got a Core and Interactivity inside it, which it uses for the actual logic of the terminal itself. TermControl's main responsibility is now
By splitting into smaller pieces, it will enable us to
* write unit tests for the `Core` and `Interactivity` bits, which we desparately need
* Combine `ControlCore` and `ControlInteractivity` in an out-of-proc core process in the future, to enable tab tearout.
However, we're not doing that work quite yet. There's still lots of work to be done to enable that, thought this is likely the biggest portion.
Ideally, this would just be methods moved wholesale from one file to another. Unfortunately, there are a bunch of cases where that didn't work as well as expected. Especially when trying to better enforce the boundary between the classes.
We've got a couple tests here that I've added. These are partially examples, and partially things I ran into while implementing this. A bunch of things from #7001 can go in now that we have this.
This PR is gonna be a huge pain to review - 38 files with 3,730 additions and 1,661 deletions is nothing to scoff at. It will also conflict 100% with anything that's targeting `TermControl`. I'm hoping we can review this over the course of the next week and just be done with it, and leave plenty of runway for 1.9 bugs in post.
## References
* In pursuit of #1256
* Proc Model: #5000
* https://github.com/microsoft/terminal/projects/5
## PR Checklist
* [x] Closes#6842
* [x] Closes https://github.com/microsoft/terminal/projects/5#card-50760249
* [x] Closes https://github.com/microsoft/terminal/projects/5#card-50760258
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated
## Detailed Description of the Pull Request / Additional comments
* I don't love the names `ControlCore` and `ControlInteractivity`. Open to other names.
* I added a `ICoreState` interface for "properties that come from the `ControlCore`, but consumers of the `TermControl` need to know". In the future, these will all need to be handled specially, because they might involve an RPC call to retrieve the info from the core (or cache it) in the window process.
* I've added more `EventArgs` to make more events proper `TypedEvent`s.
* I've changed how the TerminalApp layer requests updated TaskbarProgress state. It doesn't need to pump TermControl to raise a new event anymore.
* ~~Something that snuck into this branch in the very long history is the switch to `DCompositionCreateSurfaceHandle` for the `DxEngine`. @miniksa wrote this originally in 30b8335, I'm just finally committing it here. We'll need that in the future for the out-of-proc stuff.~~
* I reverted this in c113b65d9. We can revert _that_ commit when we want to come back to it.
* I've changed the acrylic handler a decent amount. But added tests!
* All the `ThrottledFunc` things are left in `TermControl`. Some might be able to move down into core/interactivity, but once we figure out how to use a different kind of Dispatcher (because a UI thread won't necessarily exist for those components).
* I've undoubtably messed up the merging of the locking around the appearance config stuff recently
## Validation Steps Performed
I've got a rolling list in https://github.com/microsoft/terminal/issues/6842#issuecomment-810990460 that I'm updating as I go.