Compare commits

..

133 Commits

Author SHA1 Message Date
Dustin L. Howett
b47e924565 Migrate spelling-0.0.21 changes from main 2021-05-24 08:33:20 -05:00
Dustin L. Howett
34ecd1757f Migrate spelling-0.0.19 changes from main 2021-05-24 08:33:20 -05:00
Dustin L. Howett
93004173ec Emit fixup debug info for internal tooling (#10151)
See MSFT-33187224 for more information.

This may impact debuggability; I have no idea how to tell.

(cherry picked from commit 89af44488f)
2021-05-24 11:49:34 -05:00
Don-Vito
458a140ad7 Fix overflow in scroll-to-bottom (#9389)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9353
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

(cherry picked from commit 1202f89399)
2021-03-15 18:28:46 -05:00
Carlos Zamora
cd68bd0dfd Change SettingsTab close icon to be smaller (#9324)
We were using the wrong close icon for the settings tab. This is a side effect of `TerminalTab` overriding `TabBase`'s implementation.

#6800 - Settings UI Epic

Closes #9317

(cherry picked from commit 4a95341caf)
2021-03-15 18:28:45 -05:00
Eric Tian
c8be59f4a9 Display correct tooltip when window is maximized (#9412)
## Summary of the Pull Request
Instead of displaying "Maximize" in the tooltip for the maximize/restore button even when the window is maximized, it now displays "Restore Down".

## References
Fixes #5693

## PR Checklist
* [X] Closes #5693
* [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
Tested manually.

(cherry picked from commit 8358f8d93f)
2021-03-15 18:25:14 -05:00
Don-Vito
0026b67dd6 Invalidate nested command with no valid subcommands (#9495)
Currently, when loading command with sub-commands that fail to parse,
we result with command that:
* Is not considered nested (has no sub-commands)
* Has no action of its own

The commit contains a few changes:
1. Protection in the dispatch that will prevent NPE
2. Change in the command parsing that will no load
a command if all its sub-commands failed to parse
3. We will add a warning in this case (the solution is somewhat
hacky, due to the hack that was there previously)

When such command is passed to a dispatch we crash with NPE.

Closes #9448

(cherry picked from commit 28da6c4ecb)
2021-03-15 18:25:03 -05:00
Don-Vito
00f4382d63 Fix selection on multi-click with no shift (#9455)
When working on #9403 I completely forgot that
double-click and triple-click should work even without shift.
Fixed it by allowing multi-selection even if not shift is pressed.

Closes #9453

## Validation Steps Performed
* [x] single click = no selection
* [x] single click and drag = selection starting from first point
* [x] single click in unfocused pane and drag = focus pane, selection starting from first point
* [x] double-click = selects a whole word
* [x] triple-click = selects a whole line
* [x] double-click and drag = selects a whole word, drag selects whole words
* [x] triple-click and drag = selects a whole line, drag selects whole lines
* [x] Shift single-click = defines start point
* [x] second Shift single-click = defines end point
* [x] Shift double-click = selects entire word
* [x] Shift triple-click = selects entire line
* [x] Shift double-click and drag = selects entire word, drag selects whole words
* [x] Mouse mode: Shift single-click = defines start point
* [x] Mouse mode: second Shift single-click = defines end point
* [x] Mouse mode: Shift double-click = selects entire word
* [x] Mouse mode: Shift triple-click = selects entire line
* [x] Mouse mode: Shift double-click and drag = selects entire word, drag selects whole words

(cherry picked from commit efc92de19f)
2021-03-15 18:23:42 -05:00
Don-Vito
57f727c01f Allow interaction with hyperlinks in mouse mode (#9396)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9117
* [x] CLA signed.
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already.

## Detailed Description of the Pull Request / Additional comments
In mouse mode:
* Underline hyperlinks
* Activate hyperlink on ctrl+click rather than sending input to VT

(cherry picked from commit 6cd4e03a58)
2021-03-15 18:23:41 -05:00
Dustin L. Howett
daa8c44505 Update C++/WinRT to 2.0.210309.3 (#9437)
This update shrinks our binaries a little bit.

From a representative build on VS 16.8:

       | App     | Control | Connection | TSE     | WT     | Total   | msix (zip) |
       | --      | --      | --         | --      | --     | --      | --         |
Before | 2610176 | 1006592 | 433152     | 1352192 | 321536 | 5723648 | 8336910    |
After  | 2532352 | 986624  | 431104     | 1312768 | 313344 | 5576192 | 8287648    |
Delta  | 77824   | 19968   | 2048       | 39424   | 8192   | 147456  | 49262      |
%Delta | 2.98%   | 1.98%   | 0.47%      | 2.92%   | 2.55%  | 2.58%   | 0.59%      |

(cherry picked from commit 3029bb8a68)
2021-03-15 18:23:37 -05:00
Don-Vito
763dab5d7f Fix selection logic with shift on multi-click (#9403)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9382
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

## Detailed Description of the Pull Request / Additional comments
The selection with shift is quite broken in 1.6.

It started with #8611 that introduces cell selection on `shift+click`.
This change resulted in the following defect:
`shift+double-click`, `shift+triple-click` select only parts  of the word.
The reason for this is that the first `shift+click` establishes the selection,
while the consequent clicks simply extend it to the relevant boundary
(aka word / line boundary)

However, the logic was broken even before #8611.
For instance, `shift+triple-click` had exactly the same handicap:
`shift+double-click` was establishing the selection and the
third click was simply extending it to the line boundary.

This PR addresses the both defects in the following manner:
upon multi-click that starts new selection we establish
a new selection on every consequent click using appropriate mode
(cell/word/line) rather than trying to extend one.
For this purpose we remember the position that started the selection.

(cherry picked from commit 83f2a3bb3d)
2021-03-15 18:23:33 -05:00
Don-Vito
86fa001dd6 Fix TermControl initialization to pre-seed working dir (#9397)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/8969
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

(cherry picked from commit 19bd0c94e7)
2021-03-15 18:23:32 -05:00
Dustin L. Howett
507a84664e Update Cascadia Code to 2102.25 (#9295)
This update fixes some issues in Cascadia Code's February update:

microsoft/cascadia-code#406 - updated anchor type to lock with the other equals-related ligatures
microsoft/cascadia-code#408 - corrected component used for glyph to align with Unicode
microsoft/cascadia-code#412 - updated locl features removing iacute_j ligature and Catalan substitution
microsoft/cascadia-code#414 - increased overlaps of middle glyph for arrow ligatures
microsoft/cascadia-code#415 - reduces width of macronbelow
microsoft/cascadia-code#416 - rolls back name ID 4 modification as JetBrains cannot process it correctly
microsoft/cascadia-code#428 - rolls back variation of the underline to prevent MVAR table generation

Full changelist:
* Repositioned tilde in related ligatures. Previously it was higher than the standard one.
* Added missing vietnamese anchors on acute and grave (futureproofing).
* Corrected / made consistent greater & less positioning in </> and <$> related ligatures.
* Otherwise reviewed hinting

(cherry picked from commit cb03b97e67)
2021-02-25 14:48:02 -08:00
Dustin L. Howett
35a9314aa5 Exclude MonarchPeasantPackage from AnyCPU/DotNet*Test configs (#9272)
Visual Studio automatically enabled this package to build in all
configurations. This results in a build error when we go to pack the WPF
control.

(cherry picked from commit 66033dcb01)
2021-02-24 11:34:43 -08:00
Dustin L. Howett
68887fc94e Hide the Command Palette's key binding label from UIA (#9234)
The command palette's list items explicitly specify the "AcceleratorKey"
property (for UIA) and set it to the key binding. Putting it in a label
inside the list item makes Narrator read it out twice ("New Tab ...
ctrl+shift+t ... ctrl+shift+t").

Addresses #7913 (to be closed when a11y re-evaluates)

(cherry picked from commit c9dea60bbe)
2021-02-24 11:24:36 -08:00
Carlos Zamora
83d35c1191 [v1.6] Add help text to 'more options' in command palette (#9262)
This adds help text (automation property) to command palette commands that expand to more options.

Addresses #7908

## Validation Steps Performed
If I have a nested command called "color tab", NVDA reads it as...
"Color tab. More options. 5 of 79."
2021-02-23 18:56:00 -08:00
Don-Vito
b54429823e Re-enable navigation in ATS (#9140)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9130
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

(cherry picked from commit 5ffb945b66)
2021-02-23 15:57:07 -08:00
Ben Constable
d7fcc56b90 Add Keyboard Navigation To Color Picker (#9144)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

Add the keyboard navigation to the color picker.
Test manually.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Tested manually

(cherry picked from commit ca226d62e2)
2021-02-23 15:56:06 -08:00
Dustin L. Howett
b6927b24e8 Remove accidental System.Core.dll from CascadiaPackage (#9153)
A bug in VS 16.8 makes the WAP packaging project copy System.Core.dll
from the CLR into all WAP packages. We don't need it, and it adds 300kb
to our package (670kb uncompressed).

VS 16.9 sets the AddAdditionalExplicitAssemblyReferences to suppress
this assembly. If we do the same, we can avoid the reference *and* be
eady for VS 16.9.

(cherry picked from commit c1f844307c)
2021-02-23 15:55:47 -08:00
Don-Vito
d33dd1ffce Teach tab to focus terminal after rename (#9162)
## Summary of the Pull Request
After rename ends (either by enter or escape) the rename box
gets collapsed and the focus moves to the next tab stop
in the TabView (e.g., new tab button).

To overcome this:
* Added RenameEnded event to TabHeaderControl
* Forwarded it as RenamerDeactivated from TerminalTab
* Registered in the TerminalPage to focus the active control upon
RenamerDeactivated if focus didn't move to tab menu.

This means, no matter how you close the renamer,
the current terminal gains the focus.

## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9160
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

(cherry picked from commit 00d1dc99e4)
2021-02-23 15:55:10 -08:00
PankajBhojwani
da5ec7b2a2 Always show the bell indicator when BEL is emitted (#9212)
Regardless of whether the bellstyle is set to visual,
show the bell indicator in the tab header

(cherry picked from commit d12a71cdf9)
2021-02-23 15:54:37 -08:00
Dustin Howett
ba534aaad6 [STABLE ONLY] Combined revert of Environment Block Changes
Revert "Fix environment block creation (#7401)"

This reverts commit 7886f16714.

(cherry picked from commit e46ba65665)

Revert "Always create a new environment block before we spawn a process (#7243)"

This reverts commit 849243af99.

References #7418

(cherry picked from commit 4204d2535c)
2021-02-23 15:54:17 -08:00
Carlos Zamora
7541b71de8 Fix command palette accessibility (#9143)
Fixes a regression of command palette accessibility. The regression was
introduced in #8377 by setting `IsTabStop` to false. Though the commands
would light up, the focus didn't technically get on the command, so the
screen reader would just read the text box.

Opened the command palette while NVDA is active. It now reads the
commands as focus moves on them.

(cherry picked from commit ac3e4bfe56)
2021-02-23 15:54:17 -08:00
Carlos Zamora
ac9d3dcfbe Update automation properties for tab header control (#9258)
This sets the automation property (name) on the tab view item we expose in XAML.

## PR Checklist
* [x] Closes #9254

## Validation Steps Performed
Tested under NVDA and Accessibility Insights

(cherry picked from commit ef4f2ca03e)
2021-02-23 15:54:16 -08:00
Dustin L. Howett
f7b205fa78 TabHeader: put TraceLogging events in the right category (#9257)
Fixes MSFT-31615100

(cherry picked from commit 17c6f8e9ff)
2021-02-23 15:54:16 -08:00
Don-Vito
ff126c5a2f Ensure hyperlinks de-underline when pointer leaves terminal (#9195)
Introduced PointerExited handler to nullify last hovered cell
(which will also de-underline the hyperlink if required).

Closes #9141

(cherry picked from commit 52d1533c4f)
2021-02-23 15:54:16 -08:00
SJ
91f68e2914 Do not dismiss selection if the Windows keys is pressed as a key-combination (#9163)
Aims to fix #8791.

<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Prior to this PR, if the Windows key was pressed as a part of a key combination, then selection was being dismissed. For example,  when a user pressed `Windows` + `Shift` + `S` keys to invoke the _Capture & Annotate_ tool.
This PR adds an exception for not clearing selection when either of the two Windows keys are pressed as part of a key combination.
It was tested manually by trying to reproduce the issue.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #8791
* [x ] CLA signed.
* [x ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #8791

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
1. Build Terminal.
2. Write anything & make a selection.
3. Press `Windows`+ `Shift` + `S` keys.
4. The _Capture & Annotate_ tool appears but the selection made in step 2 isn't dismissed (doesn't disappear).

(cherry picked from commit 7d37ba22e7)
2021-02-23 15:54:16 -08:00
Carlos Zamora
245a244f1b [v1.6 ONLY] Make Settings UI inaccessible (#9225) 2021-02-22 10:31:23 -08:00
Javier
fc57c82f80 wpf: prevent a 0 size when VS builds the Terminal window too early (#9194)
The terminal WPF container might have a (0,0) render size when VS
eagerly attempts to initialize the terminal tool window when it is
pinned during startup, but no actual UI is shown due to the VS welcome
dialog that shows up before VS can build the terminal tool window.

We've fixed this issue previously in other areas but only recently did
we get a complete enough dump to find the corner cases for this issue.

This should patch all the gaps that cause this bug.

## Validation Steps Performed
I've manually validated the scenarios shown in the user dumps.

(cherry picked from commit 12eb69f665)
2021-02-17 13:06:19 -08:00
Carlos Zamora
c24194a3f3 Bugfix: update color scheme references properly (#9103)
`CascadiaSettings::UpdateColorSchemeReferences` had two bugs in it:
1. we would never check/update the base layer
2. we would explicitly set the color scheme on a profile referencing the
   old name

This PR fixes both of those issues by checking/updating the base layer,
and ensuring that we check if a profile has an explicit reference before
updating it.

Since the affected code is in TSM, I also created an automated local
test.

## Validation Steps Performed
Bug repro steps.
Specifically tested [DHowett's scenario] too.
Test added.

Closes #9094

[DHowett's scenario]: https://github.com/microsoft/terminal/issues/9094#issuecomment-776412781

(cherry picked from commit 42511265e5)
2021-02-10 14:12:26 -08:00
Don-Vito
3ab1d724d2 Fix CommandPalette to prefer inner interactions over bindings (#9056)
* Currently TerminalPage registers on CmdPal key events:
  * To invoke bindings when the palette is open
  * Since some key combinations are not triggered by KeyDown
    it registers for PreviewKeyDown
* As a result bindings might be preferred over navigation
  (e.g., ctrl+v will paste into Terminal rather than into search box)
* To fix this, I moved all interactions inside the CmdPal into
  PreviewKeyDown as well
* In addition, added specific handling for copy/paste
  which now allow to interact with search box even if not focused

Closes #9044

(cherry picked from commit ed19301ad3)
2021-02-10 12:37:21 -08:00
Dustin L. Howett
e13d1a7b61 [1.5 ONLY] Remove the close submenu (#9102)
There's a platform issue that causes it to crash.
Fixes #8944.

(cherry picked from commit 5fdd1560fd)
2021-02-10 11:55:49 -08:00
Michael Niksa
48543bb3e7 Teach the renderer to keep thread alive if engine requests it (#9091)
Teaches renderer base to keep thread alive if engine requests it.
`DxEngine` now requests it if shaders are on.

- The render engine interface now has a true/false to return whether the
  specific renderer wants another frame to immediately follow up. The
  renderer base will ask for this information as it ends the paint on
  any particular engine (which is the time where invalid regions are
  typically cleaned up) and just poke the render thread the same as if
  an invalidation request came in from outside of render-land. That will
  trigger the render thread to just keep moving in the same way as any
  other invalidation.

## Validation Steps Performed
- [x] Actually built it
- [x] Actually try it

I promised this in #8994

(cherry picked from commit 3b247812ce)
2021-02-10 11:26:06 -08:00
Dustin L. Howett
91b867102c Work around a (likely) optimizer issue with SetPixelShaderPath (#9092)
It appears as though the optimizer is generating a sequence of
instructions on x64 that results in a nonsense std::wstring_view being
passed to SetPixelShaderPath when it's converted from a winrt::hstring.

Initially, I suspected that the issue was in us caching `_settings`
before we broke off the coroutine to update settings on the UI thread. I
implemented a quick fix for this (applying values off the new settings
object while also storing it in the control instance), but it didn't
actually lead anywhere. I do think it's the right thing to do for code
health's sake. Pankaj already changed how this works in 1.7: we no
longer (ever) re-seat the `_settings` reference... we only ever change
its parentage. Whether this is right or wrong is not for this paragraph
to discuss.

Eventually, I started looking more closely at the time travel traces. It
seriously looks like the wstring_view is generated wrong to begin with.
The debugger points directly at `return { L"", 0 };` (which is correct),
but the values we get immediately on the other side of the call are
something like `{ 0x7FFFFFFF, 0 }` or `{ 0x0, 0x48454C4C }`.

I moved _just_ the call to SetPixelShaderPath into a separate function.
The bug miraculously disappeared when I marked it **noinline**. It
reappeared when the function was fully inlined.

To avoid any future issues, I moved the whole UI thread body of
UpdateSettings out into its own function, to be called only while on the
UI thread. This fixes the bug.

Closes #8723.
Closes #9064.

## Validation Steps Performed

I found a repro (update the settings file every 0.5 seconds and resize
the terminal wildly while it's doing so) that would trigger the bug
within ~10 seconds. It stopped doing so.
2021-02-10 11:25:55 -08:00
Don-Vito
4014100b2d Prevent command palette from closing on right click (#9057)
The command palette is ephemeral and is dismissed if the focus
moves to an element which is not the palette's descendant.

Unfortunately, this breaks the (right-click) context menu,
as it  is not a child of the palette
(popups are hosted on a separate root element).

(cherry picked from commit 5f8e3d1676)
2021-02-09 11:45:59 -08:00
Sarim Khan
3778924aa4 fix tab title propagation issues (#9054)
- Fixes empty app title when  `showTerminalTitleInTitlebar` is false
- Fixes Tab title propagation to Window title when
  `showTerminalTitleInTitlebar` is false
- Fixes Tab title propagation to Window - title doesn't update when
  Window is unfocused

1. There were a missing
   `_settings.GlobalSettings().ShowTitleInTitlebar()` check. Because of
   this Title update event was being fired even when
   `showTerminalTitleInTitlebar` is false. This results in empty tab
   title to propagate to Window title. Also then after switching tabs
   back and forth, tab title propagates to window title. These shouldn't
   propagate when `showTerminalTitleInTitlebar` is false. I added the
   `showTerminalTitleInTitlebar` check in relevant logic to fix the
   behavior.

2. Code was checking `tab.FocusState() != FocusState::Unfocused` , but
   when the whole terminal window is not in focus, the active tab is
   also in Unfocused state. This was preventing tab title to propagate
   to window title when application is unfocused. I added the logic of
   checking matching selected tabs' index. This fixes the issue.

## Validation Steps Performed
I did the reproduce steps descripted in the issue to reproduce the bugs.
After applying the fixes, the bugs don't appear anymore while doing the
reproduce steps.

Closes #8704

(cherry picked from commit 47881a802f)
2021-02-09 11:45:15 -08:00
Dustin L. Howett
c01573b5d3 Update Cascadia Code to 2102.03 (#9088)
The February 2021 update of Cascadia Code fixes 23 issues and
introduces support for infinite arrow ligatures and control pictures.

(cherry picked from commit 6af49a5246)
2021-02-09 11:44:43 -08:00
PankajBhojwani
62a12eeb79 Fix crash when closing multiple splits from the tab context menu (#9028)
## Summary of the Pull Request
Fix for #9021

Turns out that what was happening is that the _parent_ pane's `Closed` event was being caught by the tab, and parent panes always have `nullopt` as their id. So now the `Pane::Id()` call always returns an optional, allowing us to check if it has a value before we access it.

## PR Checklist
* [x] Closes #9021

## Validation Steps Performed
No more crash

(cherry picked from commit ed4e829adc)
2021-02-04 16:55:53 -08:00
Lachlan Picking
e457a1a868 Fix shader time input (#8994)
Correctly sets the time input on the pixelShaderSettings struct, which was previously hard-coded to `0.0f`.

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

## Detailed Description of the Pull Request / Additional comments
I added a private field to `DxEngine` to store the timestamp for when a custom shader is first loaded. The field is initialized in `_SetupTerminalEffects()`, and the calculated time value (seconds since the timestamp) passed to the actual shader is set in `_ComputePixelShaderSettings()`.

There remains an issue with with jerky animation due to the renderer not repainting when the window contents are not updated (see discussion in the original issue).

This is basically my first time writing C++; constructive review is enthusiastically welcomed 🙂

## Validation Steps Performed
I manually tested using a variety of simple shaders that rely on time input for animation.

(cherry picked from commit 9fb4fb2741)
2021-02-04 16:55:16 -08:00
Don-Vito
1c0bbe72d3 Prevent context menu of tab renamer text box from canceling edit (#8979)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/8975
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

## Detailed Description of the Pull Request / Additional comments
* We dismiss the edit each time `HeaderRenamerTextBox` loses focus
* Unfortunately, this applies also to scenario where the context menu
(copy, paste, select, etc.) is open with the right-click
* The fix is to ignore focus loss if `HeaderRenamerTextBox().ContextFlyout()` is open.
* We can do it as upon the fly-out dismiss the text box regains the focus.

![RenamerContextMenu](https://user-images.githubusercontent.com/4639110/106394866-90b10100-6407-11eb-8e92-627be4f70500.gif)

(cherry picked from commit 636f436465)
2021-02-04 16:54:53 -08:00
Don-Vito
cf694747a0 Fix cursor restore to happen on the tab row (#8952)
Closes #8918

(cherry picked from commit 9cb8db8e9a)
2021-02-04 16:47:56 -08:00
Carlos Zamora
9444617bc6 Serialize 'disabledProfileSources' (#9038)
"disabledProfileSources" is saved to `CascadiaSettings` _not_
`GlobalAppSettings` (and, even then, it's only read when it's used,
never saved). This PR specifically detects if it was defined in
settings.json, and copies it over when the settings are serialized.

## Validation Steps Performed
1. Added "disabledProfileSources" to settings.json, then serialized. -->
   "disabledProfileSources" is now maintained.
2. Updated `CascadiaSettings` serialization test

Closes #9032

(cherry picked from commit 230fad533e)
2021-02-04 16:47:55 -08:00
Don-Vito
b80f6de58c Fix hyperlink not de-underlined in unfocused pane (#9039)
Ensures that:
* All hyperlink related logic is running on unfocused pane
* All unrelated logic is not running on unfocused pane

Closes #8925

(cherry picked from commit 40e328984d)
2021-02-04 16:47:55 -08:00
Chester Liu
34bb1cbd01 Improve OSC 9;9 parsing logic & add tests (#8934)
This PR fixes the parsing of OSC 9;9 sequences with path surrounded by
quotation marks.

Original OSC 9;9 PR: #8330

Unit test added. Manually tested with oh-my-posh.

Closes #8930

(cherry picked from commit 0811c572ae)
2021-02-04 16:47:55 -08:00
Carlos Zamora
b656fdd34f Generate settings.json if deleted while WT is open (#9012)
The settings.json was not regenerated if WT was already open. This resulted in the `ShellExecute` from trying to open the settings to pop up Notepad and say that this file didn't exist. We now detect if the settings.json was deleted to kick off loading the settings.

Closes #8955

(cherry picked from commit a5931fbead)
2021-02-04 16:47:55 -08:00
Mike Griese
41e9c59879 Manually initialize the warnings vector to prevent a crash on launch (#8995)
## Summary of the Pull Request

Oops, winrt `IVector`s need to be manually initialized, when default-constructed `std::vector`s didn't. Simple oversight.

## PR Checklist
* [x] Closes #8986
* [x] I work here
* [x] A test would be great but ain't nobody got time for that.
* [n/a] Requires documentation to be updated

## Validation Steps Performed
Ran the terminal with
```json

    "schemes" :
    [ {} ]
```
First the crash repro'd, now it doesn't.

(cherry picked from commit 9d71fa817d)
2021-02-04 16:47:55 -08:00
James Holderness
680c7deb2f Fix crash in terminal when tab closed while data is being output (#8982)
If there is data being output when a tab is closed, that can sometimes
result in the application crashing, because the renderer may still be in
use at the time is it destroyed. This PR attempts to prevent that from
happening by adding a lock in the `TermControl::Close` method.

What we're trying to prevent is the connection thread still being
active, and potentially accessing the renderer, after it has been
destroyed. So by acquiring the terminal lock in `TermControl::Close`,
after we've stopped accepting new output, we can be sure that the
connection thread is no longer active (it holds the lock while it is
processing output). So once we've acquired and released the lock, it
should be safe to tear down and destroy the renderer.

## Validation Steps Performed

While this crash is difficult to reproduce in general usage, it occurred
quite frequently when testing my `DECPS` implementation (there is some
tricky thread synchronisation, which seems more likely to trigger the
issue). With this patch applied, though, those crashes have stopped
occurring.

I've also stepped through the shutdown code in the debugger, manually
freezing threads to get them aligned in the right way to trigger the
crash (as explained in issue #8734). Again with the patch applied, I can
no longer get the crash to occur.

Closes #8734

(cherry picked from commit 40ebe5ab54)
2021-02-04 16:47:55 -08:00
hereafter
e9548969e4 Fix crash in explorer background context menu logic (#8977)
Fix a bug brought in with PR: #8638

see,
#8936
#8638

* [x] Closes #8936
* [x] CLA signed
* [x] Tests passed

With the help from @nc-x, the issue is reproduced and fixed by this patch.

CLSCTX_IN_PROCESS is not good enough for all cases to create IShellWindows interface.
Put a CLSCTX_ALL fixes the issue.

Another debugging warning dialogs  for reusing not null com_ptr in the loop is fixed too.
(This was shown in debug builds only)

(cherry picked from commit e207236713)
2021-02-04 16:47:55 -08:00
Carlos Zamora
7fe98a0344 Bind LaunchMode and TabSwitcherMode properly in SUI (#8956)
## Summary of the Pull Request
Properly binds `CurrentLaunchMode` and `CurrentTabSwitcherMode` in the Settings UI. The default mode is `OneTime`, resulting in the setting never being set.

I performed a regex search of all "SelectedItem" bindings and these were the only two that were not properly bound.

## References
#6800 - Settings UI Epic

## Validation Steps Performed
Modified tab switcher mode and launch mode via the settings UI. Then saved. Before, the settings would revert back and not get applied. Now they got applied.

Closes #8947

(cherry picked from commit 597a3325ea)
2021-02-04 16:47:55 -08:00
Kayla Cinnamon
5b1137ccff Add scrollToTop and scrollToBottom actions to JSON schema (#8923)
Closes #8992.

(cherry picked from commit b502e0e530)
2021-02-04 16:47:55 -08:00
Javier
96e0232603 wpf: Add a TerminalControlSize emptiness check (#8906)
Watson reports show that an "ArgumentException" is being thrown due to `renderSize`
not being valid. Added a check for renderSize before attempting to resize.
2021-01-26 13:57:30 -08:00
Carlos Zamora
ae8347f336 Allow ClosePane to close the Settings UI (#8903)
This is an extension of #8885. A lot of users have grown accustomed to
using `closePane` to close a tab. This adds `closePane` to the list of
keybindings accepted by #8885, and modifies the `closePane` code to
close the Settings UI if we are in a `SettingsTab`.

## References

#6800: Settings UI Epic
#8885: PR - Settings UI should respect key bindings (temporary solution)
#8882: Issue - Settings UI should respect key bindings
2021-01-26 19:14:28 +00:00
Mike Griese
3f1262e4c2 Fix the erroneous TargetRuntime causing a warning in VS (#8895)
## Summary of the Pull Request

Apparently, we don't need this `TargetRuntime`. That's what was causing VS to think that we were a C# project, and give us that warning. This is the solution we got from the owner of the `.wapproj` plugin.

## References
* Introduced in c33883d852, in PR #8062

## PR Checklist
* [x] Closes #8301
* [x] I work here


Build and ran it fine. Changed TermControl, built and ran it fine. Now let's hope CI likes it.
2021-01-26 19:13:59 +00:00
PankajBhojwani
b50df20cfe Add a slider for the padding setting in the SUI (#8851)
Replaces the textbox used for the padding setting with a slider

References #8764
2021-01-26 02:22:24 +00:00
Mike Griese
7b6958405e Fix the Profiles pivot selection, again (#8844)
This reverts the revert in #8838.

The problem was that the `Profile` in the singleton nav state would be
updated before the binding fired, so we'd end up modifying the _new_
profile, because both the old page and the new page would be pointing at
the _new_ profile already.

Instead of using a singleton instance of the profile nav state, we'll
create a new one each time. The new nav state attempt to steal the
selected pivot from the last instance of the nav state, if the profiles
are the same. This means that 

This means that we won't end up modifying the new profile. The old
page's nav state will still have the old profile, so it'll still end up
modifying the old `ProfileViewModel`.

## PR Checklist
* [x] I work here
* [x] Tested manually
* [x] Fixes the first point in #8769, again
2021-01-25 15:06:33 -08:00
Don-Vito
65269c8311 Teach SUI tab to respect close / prev / next tab bindings (#8885)
A temporary solution for #8882
2021-01-25 14:52:12 -08:00
Don-Vito
3e5d6e71a2 Fix crash in command palette in high-contrast mode (#8886)
Fixing a typo I made (affecting only high contrast mode)

Closes #8884
2021-01-25 14:51:11 -08:00
Don-Vito
dff8f15efa Teach tab tool tip to show profile name (#8883)
Closes #4478
2021-01-25 22:50:21 +00:00
Dustin Howett
2b27d4ce91 Add DECID to spellbot 2021-01-25 11:38:33 -08:00
James Holderness
172d9a7f64 Add support for the DECID report (#8864)
This PR adds support for the `DECID` (Identify Device) escape sequence,
which allows for querying the terminal type in a way that is backwards
compatible with VT52 terminals.

This simply checks for the `ESC Z` sequence in the `ActionEscDispatch`
method of output state machine, and forwards the query to the existing
`DeviceAttributes` dispatch method, since the expected response is
identical to a `DA` report.

## Validation Steps Performed
I've added an output engine test that verifies that the `ESC Z` sequence
is correctly interpreted as a `DA` query when in ANSI mode, and as a
VT52 identification query when in VT52 mode.

Closes #8857
2021-01-25 18:11:33 +00:00
PankajBhojwani
02fd7a0c15 Show indicator of bell in tab (#8637)
When we emit a BEL (visual or audible), show an indicator in the tab
header

If the tab the BEL is coming from is not focused when the BEL is raised,
the indicator in its header will be removed when the tab gains focus. If
the tab was already focused when the BEL was emitted, then the indicator
goes away after 2 seconds.

Closes #8106
2021-01-22 23:48:20 +00:00
PankajBhojwani
acf36d0e4f sui: Improve the "use parent process directory" checkbox (#8827)
Make the "use parent process directory" checkbox rely on a computed
property in the ProfileViewModel. It will be enabled when the starting
directory is empty and disabled when it's not. When it's unchecked, the
last-used value will be restored. If there is no last-used value, it
will be set to %USERPROFILE%.

Closes #8805
2021-01-22 23:46:41 +00:00
Carlos Zamora
d45cc4c2e1 [Settings UI] Allow renaming a profile (#8837)
This adds a "Name" text box to the Profile page in the Settings UI.
Any changes to the name/icon are propagated to the relevant
NavigationViewItem.

Base Layer does not have a "Name".

## References
#6800 - Settings UI Epic
2021-01-22 23:43:54 +00:00
Carlos Zamora
9700598ecb Bugfix: sync color scheme rename with profile reference (#8793)
## Summary of the Pull Request
This fixes a bug where renaming/deleting a color scheme would not update profiles that referenced it.

This also adds detection for renaming a color scheme to a name that is already in use, and adds appropriate UI for that.

## References
#6800 - Settings UI Epic

## PR Checklist
* [X] Closes #8756 

## Detailed Description of the Pull Request / Additional comments
`Model::CascadiaSettings` was updated to have a `UpdateColorSchemeReferences()` function that updates all profiles referencing the newly renamed color scheme.

`Editor::ColorSchemesPageNavigationState` now takes and exposes a `Model::CascadiaSettings`.

When a color scheme is renamed or deleted, we use `CascadiaSettings` to update our list of color schemes appropriately, then call `UpdateColorSchemeReferences()` to update the profiles.

The tricky part is that `Profile` does not store a direct reference to `ColorScheme`, but rather the name of the color scheme. See [this tread](https://github.com/microsoft/terminal/issues/8756#issuecomment-760375027) for a discussion on this topic.

## Validation Steps Performed
Repro steps from #8756 when renaming/deleting a referenced color scheme.

## Demo
![Scheme Name Already In Use Demo](https://user-images.githubusercontent.com/11050425/105431427-6e023980-5c0a-11eb-894a-42152fc77f05.gif)
2021-01-22 18:21:18 +00:00
Chester Liu
124cbd9e47 Add skeleton code for bracketed paste mode (#8840)
This adds the skeleton code for "bracketed paste mode" to the Windows
Terminal. No actual functionality is implemented yet, just the wiring
for handling DECSET/DECRST 2004.

References #395
Supersedes #7508
2021-01-22 05:11:11 +00:00
Carlos Zamora
b6f5f2a323 Revert "Maintain current Pivot selection... (#8803)" (#8838)
This reverts commit a7d7362b95 introduced by #8803.

Reverting this commit fixes #8836 at the expense of the profile page
remembering the last Pivot selection. The 

## References
#6800 - Settings UI Epic

#8803 maintained a `ProfilePageNavigationState` in `MainPage` to remember
the pivot position. However, the two-way binding on the TextBoxes
now seem to happen too late (after the navigation occurs),
resulting in the text being applied to the wrong profile.  In other
words, the sequence of events probably looks something like this:

1. user types text (_state.profile = old profile)
2. user moves to new profile
3. navigation completes (_state.profile = new profile)
4. textbox two-way binding fires, setting _state.profile.WHATEVER = value

## Validation Steps Performed
Performed repro sets from #8836. Bug no longer occurs. 

Reopens #8769
Closes #8836
2021-01-21 10:50:49 +00:00
Don-Vito
9c4950cb32 Teach terminal to hide mouse while typing (#8629)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/5699
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. 

## Detailed Description of the Pull Request / Additional comments
* Use SPI_GETMOUSEVANISH setting
* Hide upon CharacterReceived
* Unhide upon PointerMoved, PointerMoved, MouseWheel, LoseFocus
2021-01-21 01:17:59 +00:00
Carlos Zamora
3b53c6956c Bugfix: navigate to new profile ref on settings reload (#8835)
Upon a settings reload, we would select the correct navigation item for
a profile, but navigate to the old one. As a result, you would navigate
to the old page that points to a dead profile object. This would make it
appear like you did not discard/save the changes.

This bugfix navigates to the newly created profile, ensuring that your
changes are actually applied to the settings model's clone in use.

## References
#8773 - Introduced the bug
#6800 - Settings UI Epic

## Validation Steps Performed
- Navigate to "powershell" profile
- edit "tab title" value
- discard changes

Before: changes would persist unless you discarded changes again
Now: changes are discarded

Also verified expected behavior occurs when you click "save" instead of
"discard"
2021-01-21 00:54:59 +00:00
Kayla Cinnamon
98d0613124 Include UWP styling guidance in settings UI (#8831)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
The settings UI was _close_ to looking just right, it just needed some tweaks to adhere to the proper guidance: https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/forms

This PR changes the font sizes, spacing, and layout of all of the pages to align with guidance.

Some pics:
![image](https://user-images.githubusercontent.com/48369326/105241313-58194980-5b22-11eb-9f18-524cc988ec33.png)

![image](https://user-images.githubusercontent.com/48369326/105241331-60718480-5b22-11eb-8698-b9fadf3c3016.png)

![image](https://user-images.githubusercontent.com/48369326/105244413-57ce7d80-5b25-11eb-87c3-ee5f19417318.png)

![image](https://user-images.githubusercontent.com/48369326/105241384-73845480-5b22-11eb-9517-4010b145ffc2.png)

Min width:
![image](https://user-images.githubusercontent.com/48369326/105241406-7aab6280-5b22-11eb-9c59-ffc72f66509d.png)

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

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

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
* Removed custom font sizing, WinUI adheres to guidance anyway
* 24px spacing between controls and 48px between groupings
* Controls shouldn't be next to each other (see Launch size)
* Technically Launch size is a grouping, so it gets upgraded to subtitle status
* Left margins for pages have been fixed to left align with the page titles
* Single checkboxes have been changed to toggle switches

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-01-20 22:42:39 +00:00
Dustin L. Howett
e7592ec3d4 ROW: clean up in preparation to hide CharRow & AttrRow (#8446)
Moving things out of CharRow into ROW helps us hide it as an implementation detail.
This is part one of many.

### CharRow: Hide ClearCell, use ROW::ClearColumn

### CharRow: Hide GetText, use ROW::GetText

### CharRowBaseTests: remove dead file (never used!)

### CharRow: Move DoubleBytePadded into ROW

### CharRow: Move WrapForced into ROW

### Char/AttrRow: Hide Reset, use ROW::Reset

### Remove RowCellIterator (dead code)

RCI was unused; it was replaced by TextBufferCellIterator shortly after its creation

### Move AttrRowTests to ut_textbuffer from ut_host

It had no reliance on the host.
2021-01-20 21:16:56 +00:00
Raphael Horber
b7a7aa0bc3 Add doubleUnderscore cursor style (#7827)
Adds a new cursor type "doubleUnderscore". Tested manually.

Closes #6786
2021-01-20 19:36:07 +00:00
Michael Niksa
0fa286c011 Cleanup NuGet.Config file. (#8829)
Cleans up a ton of competing and outdated sources from our NuGet.Config to improve reliability and maintainability.

## PR Checklist
* [x] Closes an overdue-to-clean mess.
* [x] I work here.
* [x] Tests covered below.
* [x] I've discussed this with @DHowett already.

## Validation Steps Performed
- [x] - NuGet restored everything I could get my hands on (all `packages.config`) in our project before and after the change.
- [x] - The build and tests still run fine. (PR automation should check this one)
2021-01-20 19:00:36 +00:00
Don-Vito
12b12d5b07 Fix tab selection to bring the tab into view (#8832)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/3638
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already.

##Detailed Description of the Pull Request / Additional comments
A workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/3945.
Thanks to @michael-hawker and @chingucoding for the suggested solution.
2021-01-20 17:37:47 +00:00
Don-Vito
f196285824 Move Tab Switcher mode handling into CommandPalette (#8656)
A part of the #8415.
Includes:
* Moving `TabSwitcherMode` related decisions into `CommandPalette`
(simplifying the logic of `TerminalPage::SelectNextTab`)
* Fix a bug where the index of first tab switch is incorrect
(since bindings are not updated)
* Removing redundant `CommandPalette` updates
* Preparations for tabs binding
2021-01-19 22:18:10 +00:00
Carlos Zamora
9fed14a95e Fix refresh-related crashes in Settings UI (#8773)
Removes the visibility hack in `UpdateSettings` where we were hiding
Profile menu items instead of removing them. This hack was removed using
`ReplaceAll`. For an unknown reason, calling `Remove()` would result in
an out-of-bounds error in XAML code.

The "Discard" button would improperly refresh the Settings UI. Both of
the bugs were caused by holding a reference to a hidden menu item then
trying to set the `SelectedItem` to that menu item. 

Additionally, 9283375 adds a check for the selected item in
`SettingsNav_ItemInvoked()`. This prevents navigation to an already
selected item. This was the heuristic used by the XAML Controls Gallery.

References #6800 - Settings UI Epic

## Validation Steps Performed
(Repeated for each menu item)
1. Select the menu item
2. click "Discard changes"
3. Verify navigated to same page

Also performed repro steps for #8747 and #8748.

Closes #8747
Closes #8748
2021-01-19 22:14:07 +00:00
Mike Griese
c33a97955f Add a Monarch/Peasant sample app (#8171)
This PR adds a sample monarch/peasant application. This is a type of
application where a single "Monarch" can coordinate the actions of multiple
other "Peasant" processes, as described by the specs in #7240 and #8135.

This project is intended to be a standalone sample of how the architecture would
work, without involving the entirety of the Windows Terminal build. Eventually,
this architecture will be incorporated into `wt.exe` itself, to enable scenarios
like:
* Run `wt` in the current window (#4472)
* Single Instance Mode (#2227)

For an example of this sample running, see the below GIF:

![monarch-peasant-sample-001](https://user-images.githubusercontent.com/18356694/98262202-f39b1500-1f4a-11eb-9220-4af4d922339f.gif)

This sample operates largely by printing to the console, to help the reader
understand how it's working through its logic.

I'm doing this mostly so we can have a _committed_ sample of this type of application, kinda like how VtPipeTerm is a sample ConPTY application. It's a lot easier to understand (& build on) when there aren't any window shenanigans, settings loading, Island instantiation, or anything else that the whole of `WindowsTerminal.exe` needs

* [x] I work here
* [x] This is sample code, so I'm not shipping tests for it.
* [x] Go see the doc over in #8135
2021-01-19 21:55:30 +00:00
Mike Griese
a7d7362b95 Maintain current Pivot selection when saving on the Profiles page (#8803)
This PR Makes sure that after you save the settings, we stay on the same part of the profiles pivot. We do this by having a singleton `ProfilesNavigationState`, a bit like the color scheme one in #8799. Hence why this PR is targeting the other.

## PR Checklist
* [x] I work here
* [x] Tested manually
* [x] Fixes the first point in #8769
2021-01-19 21:55:06 +00:00
Mike Griese
9293867a06 Persist selected color scheme on navigation; Don't gray-out color swatches (#8799)
## Summary of the Pull Request

This PR fixes two of the components of #8765. 

> * [ ] Edit a color scheme -> Hit 'apply' -> the selected color scheme resets to the first color scheme in the list (instead of the one just edited)

This was fixed by storing the navigation state as a singleton in MainPage, and having the color schemes page update the selected scheme on that singleton. That way, a subsequent navigation to the schemes page could re-use the existing state.

> * [ ] The buttons turn gray on rollover covering up what color I'm looking at (I have dark mode)

This one was tricky. We're binding the resource for this button, to the color the button is bound to. We're also running a converter on that color, as to change the alpha slightly. This allows us to still have visual feedback on pointerover, without obscuring the color entirely. 

## PR Checklist
* [x] I work here
* [x] Tested manually
2021-01-19 19:18:07 +00:00
Dustin L. Howett
2919d96c21 Give til::bitmap custom allocator support and add til::pmr::bitmap (#8787)
`til::details::bitmap<Allocator>` will use `Allocator` for its
`dynamic_bitset`, and it will use a rebound allocator for its run storage.

Allocator should be an allocator type storing `unsigned long long`, the
backing store type for `dynamic_bitset`.

I've introduced a type alias, `til::bitmap`, which papers over the
allocator choice for all existing code. I've also introduced a second
type alias, `til::pmr::bitmap`, which lets a consumer use the C++
polymorphic allocator system.

I chatted with @miniksa about whether to keep the "full" allocator
version in `details` or not. We decided that for the simplicity of the
`til` namespace, we would. If anybody has a compelling reason to use
`til::details::bitmap<Allocator>` directly, we can re-evaluate this
decision.
2021-01-19 18:24:39 +00:00
Kayla Cinnamon
6c4878c8d5 Update tooltips and setting names in SUI (#8777)
Change wording for some settings and tooltips to make them clearer.

Closes #8746
2021-01-19 12:14:13 +00:00
Don-Vito
90e7c28069 Teach tab tool tips to show key bindings (#8810)
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/2886
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. 

## Detailed Description of the Pull Request / Additional comments
Currently the tab tool tip is the tab's title.
The PR teaches the TabBase to check if there is a switch to tab command 
associated with the current tab index,
if so concatenates the the relevant mapping to the too tip.

Of course, prefers user defined bindings to the default ones.

Moved tool tip logic to TabBase so SettingsTab has tooltip as well.

![TabToolTip](https://user-images.githubusercontent.com/4639110/104823154-a1cb1100-5850-11eb-9dbd-bf23f5e6979d.gif)
2021-01-19 11:44:04 +00:00
Leonard Hecker
de49cf1d0d Fix #8695: til::spsc assignment operators don't return anything (#8811)
The following code didn't previously work as the assignment operators
didn't return a self reference:

```cpp
auto channel = til::spsc::channel<QueueItem>(100);
auto producer = std::move(channel.first);
channel.first = std::move(producer);
```

## Validation Steps Performed

I've added a basic smoke test for `til::spsc`.

Closes #8695
2021-01-19 11:41:08 +00:00
Mike Griese
2b4b8dd1bd Polish the Background Image settings (#8778)
## Summary of the Pull Request

Two parts:
* Hide the BG image settings when no image is specified
* Add a checkbox for "Use desktop wallpaper". When that's checked, the BG image path input is hidden. Unchecking that box restores the path to what it was before.

## PR Checklist
* [x] Closes #8763
* [x] I work here

## Validation Steps Performed
Tested manually
2021-01-18 22:34:07 +00:00
Dustin L. Howett
fa0cd8c7ed Add some tests for TextBuffer::Reflow (#8715)
This is by no means comprehensive. It will be unmarked as draft when it
is more comprehensive.

This pull request adds some tests for resizing a TextBuffer and
reflowing its contents. Each test takes the form of an initial state and
a number of buffers of different sizes. The initial state is used to
seed the first TextBuffer, and the subsequent buffers are only used to
compare.

I manually reimplemented some of the DBCS logic to ensure that the
buffers contain _exactly_ what they're supposed to. I know this is
non-ideal. After some of the CharRow changes in #8446 land, this will
need to be updated.

There's a cool bit of TAEF gore in here: the IDataSource. An IDataSource
allows us to programmatically return test cases. It's a code-only
version of its support for parameterized tests of the form `Data:x = {0,
1, 2}` . 

The only downsides are...

1. It looks like COM (it is not using COM under the hood, just the COM
   ABI)
2. Property values must be returned as strings.

To best support rich test types, I used IDataSource to produce _a lit of
array indices_ and nothing more. The test is run once for array member,
and it is the test's responsibility to look up the object to which that
index refers.

Works great though! Each reflow test is its own unit, and a failure in
an earlier reflow test will not tank a later one.
2021-01-18 21:51:29 +00:00
Don-Vito
3c044f20cf Introduce startupActions in settings (#8770)
Procedural solution for https://github.com/microsoft/terminal/issues/756.

Introduces a `startupActions` global setting. 

This setting is as string with the same format as actions in command line arguments.
It is used only if command line arguments were not provided
(aka running pure wt.exe).

The setting allows implicit new-tabs.
In the case of invalid syntax we show the warning dialog and ignore the setting.

The documentation PR is here: https://github.com/MicrosoftDocs/terminal/pull/217
2021-01-15 18:30:11 +00:00
Javier
9aea904229 Added negative value check for resize newsize (#8792)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Adds a negative value check for when the terminal window is hidden/show in VS

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References
[Bug 1265984](https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265984): [Terminal] VS crashes when clicking the hidden terminal tab

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manual validation.
2021-01-15 17:48:14 +00:00
PankajBhojwani
f7b5ff322a Add missing settings to the settings UI (#8774)
Adds the tab switcher mode setting and copy format setting to the SUI

Closes #8755
2021-01-14 23:57:59 +00:00
PankajBhojwani
9905bd5f09 Add missing functionality to SUI (#8786)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Add checkbox for 'inherit from parent process' for starting directory
When checked, the textbox and browse button are disabled
If the starting directory is empty, the checkbox is automatically checked

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
<img width="328" alt="pardir1" src="https://user-images.githubusercontent.com/26824113/104529798-64038980-55bf-11eb-93fd-75e6cf1e2547.png">
<img width="317" alt="pardir2" src="https://user-images.githubusercontent.com/26824113/104529803-66fe7a00-55bf-11eb-89b6-5b35c8ab89b8.png">
2021-01-14 19:06:10 +00:00
PankajBhojwani
e0f585251a Show cursor shape in SUI (#8780)
Show the cursor shape next to the text for it

References #8764 

<img width="413" alt="cursorshape" src="https://user-images.githubusercontent.com/26824113/104498936-36075080-5591-11eb-8403-608e6ae3fac7.png">
2021-01-14 13:34:52 +00:00
Carlos Zamora
9b1bb134bf Order enum settings according to enum values (#8784)
Sorts the list of `EnumEntry`s that is used to create combo boxes and
radio buttons in the Settings UI. `INITIALIZE_BINDABLE_ENUM_SETTING`
sorts the list in increasing order of the enum values, whereas
`INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER` does so in decreasing
order.

## References
#6800 - Settings UI Epic

I attempted sorting the `IObservableVector<EnumEntry>` using
`std::sort`, but I would get an error in
`winrt::Windows::Foundation::swap` (`C2665`). So instead, I did the
following approach:
- (unchanged) we're converting the `IMap` from EnumMappings into (1) a
  map of localized strings and (2) the list for XAML controls
- instead of storing `EnumEntry`s to the `IObservableVector` directly,
  store it to a `std::vector`
- sort the vector using `std::sort`
- _now_ initialize the `IObservableVector` using the sorted
  `std::vector`

This uses the value of the associated enum to determine a sorting order.
Since we want the "negative" value (i.e. "none" or "hidden") to be last,
I use `EnumEntryComparator` and `EnumEntryReverseComparator` to
determine whether we want increasing or decreasing order respectively.
`INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER` is a copy of
`INITIALIZE_BINDABLE_ENUM_SETTING`, except it uses
`EnumEntryReverseComparator` to sort in decreasing order.

Closes #8758
2021-01-14 11:47:33 +00:00
PankajBhojwani
e851c61777 Remove setting for close all tabs pop up (#8779)
Remove the setting for enabling/disabling the dialog that
shows up to confirm the closing of all tabs in the SUI

Closes #8757
2021-01-14 03:01:17 +00:00
PankajBhojwani
f8ccf64252 Polish Global settings a bit (#8783)
Polish for global settings in SUI

- Launch size can no longer be negative
- Reorder appearance options

## PR Checklist
* [x] Closes #8766
2021-01-14 01:34:43 +00:00
PankajBhojwani
5f590a5efa sui: hide commandline and hidden option in base layer (#8782)
Referenced #8764.
2021-01-13 17:33:08 -08:00
Mike Griese
bf783842f2 Move the window settings below the acrylic settings (#8775)
![image](https://user-images.githubusercontent.com/18356694/104447754-d6de1780-5561-11eb-8d99-71f4ec5ad429.png)


Put some settings below the acrylic & background image settings. Those groups have controls that will become visible when the user enables the setting. If there's other controls below them, then it's less likely that the user has _exactly scrolled to the checkbox_. That means it's more likely that the newly visible controls will be on-screen.

* [x] closes one checkbox in #8764
* [x] I work here
2021-01-13 18:08:40 +00:00
Mike Griese
20bfccefb7 Fix the duplicate .xbf error after a TerminalControl build (#8754)
Whenever you'd make a change to anything in the Terminal Control
project, then tried deploying the package, you'd get errors like
"Package contains two files with the same name and different content,
files are `Thing.xbf` and
`.../bin/x64/debug/TSM/TerminalControl/Thing.xbf`". It seems like
`GetPackagingOutputs` was double counting these xbfs as being both from
TerminalControl and also TSM&TSE. So if you'd change TerminalControl,
it'd change the xbf files, but not the ones in TSM/TSE, and then
eventually the wapproj would fail to put it all together.

This combination of flags seems to
* make mdmerge work
* make the packaging project work
* make a partial rebuild of TerminalControl followed by a deploy work

I'm hoping that this PR build will confirm that this works in CI as well.

## PR Checklist
* [x] Fixes this minor annoyance I've been having for the past 2 months
* [x] I work here

## Validation Steps Performed
Validated locally on VS 16.8.3. Sure to break by 16.9 🙃.
2021-01-13 01:23:17 +00:00
Mike Griese
9b636edad2 Enforce a min/max on the Profile page's FontSize input (#8772)
Closes #8759
2021-01-13 00:13:28 +00:00
Mike Griese
cb2cd7e219 Reset the size param between split-pane subcommands (#8753)
## Summary of the Pull Request

I forgot to reset the `--size` argument to `split-pane` when I added it. This PR fixes that, and adds a test so I don't regress it again.

## References
* Missed in #8543


## PR Checklist
* [x] I work here
* [x] Tests added/passed
2021-01-12 23:19:55 +00:00
PankajBhojwani
aaf2395266 Show clipboard contents in multiple line paste warning dialog (#8744)
When we display a dialog to warn the user that they are doing a
multi-line paste, we show the clipboard contents

The contents are shown in a scroll viewer with a fixed maximum height.

Closes #7997
2021-01-12 23:00:27 +00:00
Don-Vito
20fc57ee0f Fix right-click paste to clear current selection (#8742)
Closes #8729
2021-01-12 22:31:59 +00:00
Don-Vito
058cbd11e7 Teach flyouts and palette to prefer user bindings over defaults (#8725)
Store the order of the bindings and upon lookup prefer the binding
that was added last.
The defaults will always "loose" to user overrides.

Closes #2991
2021-01-12 20:23:40 +00:00
Mike Griese
7235996b4d Add a move-focus subcommand (#8546)
## Summary of the Pull Request

Adds support for the `move-focus` subcommand to `wt.exe`. This subcommand works _exactly_ like `moveFocus(up|down|left|right)`. 

## References
* Will surely conflict with #8183
* Is goodness even in the world where #5464 exists

## PR Checklist
* [x] Closes #6580 
* [x] I work here
* [x] Tests added/passed
* [x] Docs PR: MicrosoftDocs/terminal#209

## Detailed Description of the Pull Request / Additional comments

Bear with me, I wrote this before paternity leave, so code might be a bit stale.

Oddly, after startup, this _does not_ leave the focus on the pane you moved to. If you `move-focus` during startup, at the end of startup, we'll still focus a _random_ pane. This is because the terminal still auto-focus a TermControl when it's done with layout. While we'll maintain the active control just fine during the startup, at the end of startup, all the controls will complete layout in a random order. 

This is no different than the startup right now. `wt sp ; sp ; sp` will focus a random pane at the end. This is left for a future someone to fix

This is also subject to #2398 / #4692. Moving in a direction isn't _totally_ reliable currently. `focus-pane -t ID` will certainly be more reliable, but this will work in the meantime?

## Validation Steps Performed

Opened probably 100 terminals, confirmed that the layout was always correct. Final focused pane was random, but the layout was right.
2021-01-11 18:37:05 +00:00
Mike Griese
bc70a97fd7 Add a spec for pane navigation (#8375)
## Summary of the Pull Request

This is a spec for "pane navigation", as we've already got a bit of an implementation in #8183. We've also had a heated discussion in Teams, and I wanted to capture a bit of that in a more formal doc. I suppose that "informal Teams chat" didn't work out in the end 😆.

Also, this is @PankajBhojwani's feature so I'm gonna let him drive. I mostly wrote this to test out a new spec template.

After discussion, we landed on proposal D, with a minor change of `last` to `prev`. This is how it was in #8183 before I started meddling 😝 

## PR Checklist
* [x] spec for #2871
* [x] I work here

## Detailed Description of the Pull Request / Additional comments

This is not my best spec ever - again, mostly just trying to spawn discussion, and prototype the new spec template.
2021-01-11 12:16:44 -06:00
Chester Liu
e557a867ee Implement ConEmu's OSC 9;9 to set the CWD (#8330)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

This PR implement the OSC 9;9 

|Sequence|Descriptoin|
| :------------- | :----------: |
|ESC ] 9 ; 9 ; “cwd” ST | Inform ConEmu about shell current working directory.|


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

#8214

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-01-11 18:01:38 +00:00
PankajBhojwani
49d008537f Fix multi line paste detection and filtering (#8634)
- Detect `\r` when warning about multi line paste
- Translate `\n` to `\r` on paste

## PR Checklist
* [x] Closes #8601
* [x] Closes #5821

## Validation Steps Performed
Manual testing
2021-01-08 10:44:16 +00:00
Dustin L. Howett
8bef5eefd5 winconpty: close the pty host handle after terminating it (#8707)
It rather raises the question as to how we missed this.

Closes #8706
2021-01-08 10:18:27 +00:00
PankajBhojwani
039c80d443 Improvements to the tab renamer box (#8589)
Basically, just impose a height on both the renamer box and the overall
tab header control. However, to ensure that the text in the tab renamer
box does not get clipped by its own border, we also need to set its font
size, which is slightly smaller than it was before but it _is_ the same
as the text block that it is trying to rename so I'd say its more
consistent now.

We also improve the tab renamer box so that it scrolls as more text is
added instead of getting truncated (when the tabWidthMode is anything
other than titleLength). When the tabWidthMode _is_ set to titleLength,
the renamer box can increase in length much more (see GIFs below).

Closes #8519
2021-01-08 02:30:05 +00:00
Don-Vito
c4c3c3116b Completely remove action dispatching from Command Palette (#8628)
Following up https://github.com/microsoft/terminal/pull/8586 by @Hegunumo,
fully remove the command dispatching logic from Command Palette.

Currently Command Palette might dispatch command in Tab Switcher mode.
This leads to several inconsistencies:
* Only the commands with the same key modifier as an ATS anchor will be issued
* This command will not close the TabSwitcher 
(while commands issued from TerminalPage do).

Implementation details:
* Pass KeyMapping rather than binding to CommandPalette
* Use this mapping inside previewKeyDownHandler of ATS to detect
if previous tab or next tab bindings were engaged. 
No need to handle Ctrl+Tab explicitly anymore - 
it is handled as any other binding.
* Cleanup the logic in TerminalPage::_SelectNextTab 
that checks if CommandPalette is visible.
It is not required anymore, as visible palette would intercept the call.
* Remove dependency of TerminalPage on AppLogic
that was introduced lately .
2021-01-07 23:09:16 +00:00
Mike Griese
7d503a4352 Add Microsoft.Terminal.Remoting.dll (#8607)
Adds a `Microsoft.Terminal.Remoting.dll` to our solution. This DLL will
be responsible for all the Monarch/Peasant work that's been described in
#7240 & #8135. 

This PR does _not_ implement the Monarch/Peasant architecture in any
significant way. The goal of this PR is to just to establish the project
layout, and the most basic connections. This should make reviewing the
actual meat of the implementation (in a later PR) easier. It will also
give us the opportunity to include some of the basic weird things we're
doing (with `CoRegisterClass`) in the Terminal _now_, and get them
selfhosted, before building on them too much.

This PR does have windows registering the `Monarch` class with COM. When
windows are created, they'll as the Monarch if they should create a new
window or not. In this PR, the Monarch will always reply "yes, please
make a new window".

Similar to other projects in our solution, we're adding 3 projects here:
* `Microsoft.Terminal.Remoting.lib`: the actual implementation, as a
  static lib.
* `Microsoft.Terminal.Remoting.dll`: The implementation linked as a DLL,
  for use in `WindowsTerminal.exe`.
* `Remoting.UnitTests.dll`: A unit test dll that links with the static
  lib. 

There are plenty of TODOs scattered about the code. Clearly, most of
this isn't implemented yet, but I do have more WIP branches. I'm using
[`projects/5`](https://github.com/microsoft/terminal/projects/5) as my
notation for TODOs that are too small for an issue, but are part of the
whole Process Model 2.0 work.

## References

* #5000 - this is the process model megathread
* #7240 - The process model 2.0 spec.
* #8135 - the window management spec. (please review me, I have 0/3
  signoffs even after the discussion we had 😢)
* #8171 - the Monarch/peasant sample. (please review me, I have 1/2)

## PR Checklist
* [x] Closes nothing, this is just infrastructure
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated
2021-01-07 22:59:37 +00:00
Kiminori Kaburagi
cceb0eaa68 Enable underlining hyperlink on hover even if not focused (#8615)
Closes #8475
2021-01-07 22:18:10 +00:00
Sato Kenta
713027b5e3 Fix color animation bug of title bar buttons (#8649)
In dark mode (and high contrast mode), color animation of title bar
button uses wrong color.  Cause of this issue is using invalid data in
`ColorAnimation`.  I fixed this bug by changing `ColorAnimation` value
in XAML layout file.

According to a [forum post], `To` value of `ColorAnimation` must be
frozen. But original source code uses "color binding" which makes this
value dynamic.  As a result, the value set by default is always used,
that means, light mode.

So I added new resource named `CaptionButtonStrokeColor` and
`CaptionButtonBackgroundColor` which has static color value.

In light mode and dark mode, I set `SystemBaseHighColor` in the color
resource.  `SystemBaseHighColor` is the same as
`SystemControlForegroundBaseHighBrush.Color` which is originally used in
animation.

The background color animation happened to work correctly because its
value is the same between light mode and dark mode.  But I also fixed
background color animation.

## Validation Steps Performed

There is no need to add new test with this fix.

- I changed the `theme` value in `settings.json` and confirmed that the
  correct color values were used.
- I confirmed that it works correctly even if the Windows theme is
  changed.

[forum post]: https://social.msdn.microsoft.com/Forums/vstudio/en-US/027c364f-5d75-424f-aafd-7fb76b10b676/templatebinding-on-storyboard?forum=wpf

Closes #7314
2021-01-07 21:12:29 +00:00
hereafter
fcca88ab25 make "open terminal here" context menu work for directory background (#8638)
This commit makes "Open in Windows Terminal" Context menu work again for
directory background even on system that OS fix is not applied.

This is a fallback solution to OS fixes mentioned in #6414.
While OS fix is on its way, we need a fallback that works on existing OS
versions.

The approach to this is: when no item is selected (nullptr for
IShellItemArray*), we use shell api to query the path of current active
Explorer window. A special case is handled for Windows Desktop. Once
we are able to obtain the path, we launch Windows Terminal with it.

## Validation Steps Performed
1. Right click on desktop to bring up the Context menu, pick "Open in
   Windows Terminal", verify that a terminal is opened with correct
   initial path.

2. Open a few File Explorer windows, pick any window, navigate to a
   folder, click on "Background" to bring up the context menu, click
   "Open in Windows Terminal" verify that a terminal is opened with
   correct initial path.

Closes #6414
2021-01-06 19:59:30 +00:00
Michael Niksa
6b2ae625a5 Initialize stack variables. check return code from shell lnk loading (#8712)
## References
* Commit f273aa679d4d9fb516678fc7ed5fc6495a8e8532 from os repository.

## PR Checklist
* [x] Closes #7650 
* [x] I work here.
* [x] Tests passed

## Detailed Description of the Pull Request / Additional comments
* We found and fixed this in November 2017, but I fumbled the replication and accidentally overwrote the commit. This digs it up from history and puts it back in our code.
* When the shortcut file cannot be read for whatever reason, we are setting the hotkey value to uninitialized data as we never initialize several members on the stack in this function. It just so happens that the one in the `dwHotkey` field is commonly `0x4c` or the letter `L` which is then sent into the window procedure to tell the OS to capture it as a global hotkey and foreground the `conhost.exe` that was started. We should realize the load failure and not set any hotkey at all and we should initialize the stack variables. This does both.

## Validation Steps Performed
* [x] Manual scenario running LNK file that cannot be loaded from customer report (make LNK to cmd.exe, make it inaccessible to conhost so it can't load/read it.)
* [x] Manual scenario with `mklink`'d link that makes the shortcut parser attempt to load (and fail because it isn't a LNK at all from #7650)
2021-01-06 12:03:45 +00:00
Michael Niksa
a8b4044630 Use memory pool for PolyTextOut items in GDI Renderer (#8619)
Converts the poly text out string and width buffers to use a memory pool since we free/alloc those every frame and are just going to reuse them over and over. 

## PR Checklist
* [x] Supports #3075
* [x] I work here.
* [x] Profiled memory before/after. Tested manually with `big.txt`.

## Detailed Description of the Pull Request / Additional comments
- Sets up a PMR memory pool for the GDI Engine. It tends to alloc and free a bunch of little buffers during painting frames. The pool will likely hold onto that memory frame over frame, but we'd just be using it again and again and again anyway. So this way we avoid all the system memory allocator locks and syscalls.

## Validation Steps Performed
- Ran `big.txt` about 10x in the window. Checked WPR/WPA profile output before/after.
2021-01-05 22:10:06 +00:00
Michael Niksa
f087d03eb2 Reduce Transient Allocations during Bulk Text Output (#8617)
Make a few changes to memory usage throughout the application to reduce transient allocations from the `big.txt` test from ~213,000 to ~53,000.

## PR Checklist
* [x] Supports #3075
* [x] I work here.
* [x] Tested manually and WPR'd. Test suite should still pass.
* [x] Am core contributor

## Detailed Description of the Pull Request / Additional comments

Transient allocations are those that are new'd, used, then delete'd. Going back and forth to the system allocator for things we're just going to throw away or use rapidly again is a performance detriment. Not only is it a bunch of time to go ask the system with a syscall, it also hits a whole bunch of locks on the allocators. This PR identifies a few places where we were accidentally allocating and didn't mean to or were allocating and freeing just to turn around and allocate again. I chose other strategies to avoid this.

## Validation Steps Performed
- Ran `big.txt` sample (~6MB file) before and after. Observed heap allocations with WPR.
2021-01-05 18:06:06 +00:00
Carlos Zamora
990e06b445 Polish OpenSettings action for Settings UI and Profile page navigation on refresh (#8670)
Performs a number of minor bugfixes related to the Settings UI:
- b5370a1 Dropdown bug:
  - the dropdown would display the keybinding for the first
    `openSettings` found. So it would accidentally present and bind the
    one for the Settings UI.
- 91eb49e autogenerated name for opening Settings UI:
  - the Settings UI keybinding would display "open settings file". This
    was updated to say "Open Settings UI".
- 1cadbf4 Profile Page navigation crash:
  - the selected item off of a MUX navigation view returns a MUX
    NavViewItem (as opposed to WUX)
-  dd2f3e5 Hookup delete for Profile page navigation:
   - missed a spot where we were manually navigating to the Profile
     page. So it wasn't hooked up properly
- 9fea6de Properly cast NavViewItem tags
  - When we update the NavigationView's menu items, we were casting the
    tags to `Model::Profile` instead of `Editor::ProfileViewModel`.

## References
#6800 - Settings UI epic

Fixes the following bug:
> - [ ] JSON change --> crash
>   - open SUI --> open JSON --> edit retro effects in JSON --> save file --> cry because the app crashed

## Additional comments
This was a part of some manual testing I performed on the Settings UI.
More intricate bugs are being reported on #6800 and will be fixed in
their own PR.
2021-01-04 14:14:51 -08:00
Taehyeok Kang
08646e5ca3 Fix Korean IME to display a character being composed in conhost (#8632)
## Summary of the Pull Request
This PR fixes Korean IME to display a character being composed in conhost.

### Before
![before](https://user-images.githubusercontent.com/58393346/102745310-03f23c80-439f-11eb-9f86-263da2dbddbb.gif)

### After
![after](https://user-images.githubusercontent.com/58393346/102745343-14a2b280-439f-11eb-853c-42b52bf442f4.gif)

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-01-04 21:05:24 +00:00
Don-Vito
68e0af41a8 Prevent Tab Switcher from increasing Tab refcount (#8653)
Fix `TabPaletteItem` to hold only a weak reference to a tab.
This way we guarantee that the refcount of the closed tab 
gets to 0 immediately
(and that command palette cannot "raise it from the dead").

While this seems a correct thing to do, 
it is still not clear why the `FilteredCommand` itself 
(the one holding the `TabPaletteItem`) doesn't get released
until the UI is refreshed.

There is an impact of not registering to PropertyChanged event:
if the tab title changes during Tab Switcher navigation
the Tab Switcher item won't be updated immediately
(the change will apply next time the Tab Switcher is open).

Due to this change we need to make sure that the tabs binding
in https://github.com/microsoft/terminal/pull/8427
doesn't break the title / icon update.

## Validation Steps Performed
* Manual testing

Closes #8651
2021-01-04 19:59:22 +00:00
Don-Vito
9b07cb8c71 Simplify TerminalPage by introducing _GetFocusedTabImpl (#8655)
A part of the https://github.com/microsoft/terminal/issues/8415.
Very technical commit to simplify the terminal page code
towards additional steps of simplifying tab management.
No business logic should change.

A firs step in splitting https://github.com/microsoft/terminal/pull/8427
2021-01-04 19:32:53 +00:00
Michael Niksa
5220738d8e Fix VT parser memory leak in tracing (#8618)
Fix memory leak that occurs from not dispatching the end of sequences on all actions (since it is buffering up all characters for trace reasons.) Also don't bother storing if no one is listening.

## PR Checklist
- [x] Closes #8283
* [x] Fixes leak found while bumbling around.
* [x] I work here.

## Detailed Description of the Pull Request / Additional comments
- We trace all the things leading up to the Action phase in the VT parser for ETW tracing to make debugging the parser easier, but we made two mistakes.
- At some point, three of the actions (related to print/execute) weren't dispatching the stored up sequence to tracing and not clearing it. So printing/executing in a giant run over and over caused the vector to bloat and bloat and bloat forever.
- We're storing things even when no one is listening. That's a waste.

## Validation Steps Performed
- Watched it grow every time I did `type big.txt` under `taskman.exe`. Then watched it not do that after.
- I did technically WPR it to figure out this was the culprit.
2021-01-04 17:14:08 +00:00
Clint Rutkas
0b0161d537 Use inclusive language in the spellcheck docs (#8677) 2020-12-29 14:13:44 -08:00
Kayla Cinnamon
683f4e28d3 Fix margins and box width in SUI (#8616)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Fixed some of the margin alignments and increased the standard box width.

![image](https://user-images.githubusercontent.com/48369326/102658433-67c0ff00-412c-11eb-8deb-cf00593169a3.png)

![image](https://user-images.githubusercontent.com/48369326/102658447-6e4f7680-412c-11eb-8371-d4dfdc63b80a.png)
2020-12-24 00:22:18 +00:00
James Holderness
fc7b052461 Add support for the "italic" graphic rendition attribute (#8580)
This PR adds support for the ANSI _italic_ graphic rendition attribute,
which is enabled by the `SGR 3` escape sequence.

For the GDI renderer, I've just created an additional italic variant of
the font, and then the `UpdateDrawingBrushes` method selects the
appropriate font variant into the device context based on the requested
text attributes.

It's a bit more complicated in the DX renderer, because we need both an
italic variant of the font, and a variant of the text format object. The
`CustomTextLayout` class also had to be updated to hold the two font and
format instances, and decide which of the variants to use based on a
`useItalicFont` property in the drawing context, initially set in the
`UpdateDrawingBrushes` method.

## Validation Steps Performed
I've created some test content using a range of different character sets
(e.g. CJK, block characters, emoji, etc.), then applied the italic
attribute mixed with various other SGR attributes to see how they
interact. The output isn't always perfect, but I think it seems
reasonable given the constraints of a cell-based terminal renderer.

Closes #5461
2020-12-18 11:23:54 -08:00
Don-Vito
8276b549e8 Allow single cell selection with Shift+Click (#8611)
Closes https://github.com/microsoft/terminal/issues/5766
2020-12-18 17:16:46 +00:00
Kiminori Kaburagi
01a04906f3 Enable shortcut while CommandPalette is open (#8586)
This commit introduces direct shortcut dispatch to TerminalPage, which
allows it to respond to key bindings before the command palette.

This allows the user to use shortcuts from the command palette while
it's open.

Closes #6679
2020-12-18 17:09:30 +00:00
Don-Vito
96b9ba99b2 Teach terminal page to go to the last used tab in MRU mode (#8610)
## PR Checklist
* [x] Closes #8550
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [x] I've discussed this with core contributors already.
2020-12-18 10:11:25 +00:00
Don-Vito
d37df8ab05 Prevent tab icon from blinking upon tab gaining focus (#8609)
Looks like recent regression:
1. Every time the tab gains focus (e.g., upon tab switching) we trigger `TaskbarProgressChanged`
2. This call results in `HideIcon` call
3. This call resets the value of Icon even if hide=false
4. This triggers reload of the image resulting in blinking
2020-12-18 08:49:22 +00:00
Mike Griese
4f46129cb4 Add size param to splitPane action, split-pane subcommand (#8543)
## Summary of the Pull Request

Adds a `size` parameter to `splitPane`. This takes a `float`, and specifies the portion of the parent pane that should be used to create the new one. 

This also adds the param to the `split-pane` subcommand.

### Examples
 
| commandline | result |
| -- | -- |
| `wt ; sp -s .25` | ![image](https://user-images.githubusercontent.com/18356694/101784317-fb595680-3ac0-11eb-8248-782dc61957cf.png) | 
| `wt ; sp -s .8` | ![image](https://user-images.githubusercontent.com/18356694/101784442-20e66000-3ac1-11eb-8f9b-fb45a73c9334.png) |
| `wt ; sp -s .8 ; sp -H -s .3` | ![image](https://user-images.githubusercontent.com/18356694/101784552-470c0000-3ac1-11eb-9deb-df37aaa36f01.png) |

## PR Checklist
* [x] Closes #6298
* [x] I work here
* [x] Tests added/passed
* [x] Docs PR: MicrosoftDocs/terminal#208

## Detailed Description of the Pull Request / Additional comments

I went with `size`, `--size,-s` rather than `percent`, because the arg is the (0,1) version of the size, not the (0%,100%) version. 

## Validation Steps Performed

Added actions, played with the commandline, ran tests
2020-12-18 03:51:53 +00:00
Carlos Zamora
2485a638cb doc: document how to add a setting to Windows Terminal (#8566)
This guide serves as a reference on how to add a setting to Windows Terminal. It covers...
- Terminal Settings Model changes
- Settings UI changes
- `TerminalSettings` changes
- Actions and action arguments

As a part of #6800 (Settings UI Follow Up Tasks)
2020-12-17 18:09:16 -08:00
Carlos Zamora
33470ad08e Add UI for adding, renaming, and deleting a color scheme (#8403)
Introduces the following UI controls to the ColorSchemes page:
- "Add new" button
  - next to dropdown selector
  - adds a new color scheme named ("Color Scheme #" where # is the number of color schemes you have)
- "Rename" Button
  - next to the selector
  - replaces the ComboBox with a TextBox and the accept/cancel buttons appear
- "Delete" button
  - bottom of the page
  - opens flyout, when confirmed, deletes the current color scheme and selects another one

This also adds a Delete button to the Profiles page. The Hide checkbox was moved above the Delete button.

## References
#1564 - Settings UI
#6800 - Settings UI Completion Epic

## Detailed Description of the Pull Request / Additional comments

**Color Schemes:**
- Deleting a color scheme selects another one from the list available
- Rename replaces the combobox with a textbox to allow editing
- The Add New button creates a new color scheme named "Color Scheme X" where X is the number of schemes defined
- In-box color schemes cannot be deleted

**Profile:**
- Deleting a profile selects another one from the list available
- the rename button does not exist (yet), because it needs a modification to the NavigationView's Header Template
- The delete button is disabled for in-box profiles (CMD and Windows Powershell) and dynamic profiles

## Validation Steps Performed
**Color Schemes - Add New**
 Creates a new color scheme named "Color Scheme X" (X being the number of color schemes)
 The new color scheme can be renamed/deleted/modified

**Color Schemes - Rename**
 You cannot rename an in-box color scheme
 The rename button has a tooltip
 Clicking the rename button replaces the combobox with a textbox
 Accept --> changes name
 Cancel --> does not change the name
 accepting/cancelling the rename operation updates the combo box appropriately

**Color Schemes - Delete**
 Clicking delete produces a flyout to confirm deletion
 Deleting a color scheme removes it from the list and select the one under it
 Deleting the last color scheme selects the last available color scheme after it's deleted
 In-box color schemes have the delete button disabled, and a disclaimer appears next to it

**Profile- Delete**
 Base layer presents a disclaimer at the top, and hides the delete button
 Dynamic and in-box profiles disable the delete button and show the appropriate disclaimer next to the disabled button
 Clicking delete produces a flyout to confirm deletion
 Regular profiles have a delete button that is styled appropriately
 Clicking the delete profile button opens a content dialog. Confirmation deletes the profile and navigates to the profile indexed under it (deleting the last one redirects to the last one)


## Demo
Refer to this post [here](https://github.com/microsoft/terminal/pull/8403#issuecomment-747545651.
Confirmation flyout demo: https://github.com/microsoft/terminal/pull/8403#issuecomment-747657842
2020-12-17 23:14:07 +00:00
Param Siddharth
c5366cea75 Minor grammatical fix (#8603)
Merely fixed the capitalization of a word.

## Detailed Description of the Pull Request / Additional comments
- Inside `doc/AddASetting.md`, on line 14, the sentence previously began with the verb "add" with a lowercase alphabet.
- I replaced "add" with "Add".
  ``` md
  [12] ...  
  [13]   2. Add matching fields to Settings.hpp
  [14]     - Add getters, setters, the whole drill.
  [15] ...
  ```
2020-12-17 15:12:36 -08:00
Kiminori Kaburagi
477c04ab94 Cleanup extraneous local leftover from #8194 (#8605)
Cleanup extraneous local leftover from #8194

## PR Checklist
* [x] Closes leftover from #8194
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA.
2020-12-17 23:06:50 +00:00
Kiminori Kaburagi
b8e6b8e27c Set Tab tooltip explicitly (#8298)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
The tab tooltip is no longer empty when it was toggle zoomed.
<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2020-12-17 14:22:19 +00:00
Austin Lamb
539a5dc0af Greatly reduce allocations in the conhost/OpenConsole startup path (#8489)
I was looking at conhost/OpenConsole and noticed it was being pretty
inefficient with allocations due to some usages of std::deque and
std::vector that didn't need to be done quite that way.

So this uses std::vector for the TextBuffer's storage of ROW objects,
which allows one allocation to contiguously reserve space for all the
ROWs - on Desktop this is 9001 ROW objects which means it saves 9000
allocations that the std::deque would have done.  Plus it has the
benefit of increasing locality of the ROW objects since deque is going
to chase pointers more often with its data structure.

Then, within each ROW there are CharRow and ATTR_ROW objects that use
std::vector today.  This changes them to use Boost's small_vector, which
is a variation of vector that allows for the so-called "small string
optimization."  Since we know the typical size of these vectors, we can
pre-reserve the right number of elements directly in the
CharRow/ATTR_ROW instances, avoiding any heap allocations at all for
constructing these objects.

There are a ton of variations on this "small_vector" concept out there
in the world - this one in Boost, LLVM has one called SmallVector,
Electronic Arts' STL has a small_vector, Facebook's folly library has
one...there are a silly number of these out there.  But Boost seems like
it's by far the easiest to consume in terms of integration into this
repo, the CI/CD pipeline, licensing, and stuff like that, so I went with
the boost version.

In terms of numbers, I measured the startup path of OpenConsole.exe on
my dev box for Release x64 configuration.  My box is an i7-6700k @ 4
Ghz, with 32 GB RAM, not that I think machine config matters much here:

|        | Allocation count    | Allocated bytes    | CPU usage (ms) |
| ------ | ------------------- | ------------------ | -------------- |
| Before | 29,461              | 4,984,640          | 103            |
| After  | 2,459 (-91%)        | 4,853,931 (-2.6%)  | 96 (-7%)       |

Along the way, I also fixed a dynamic initializer I happened to spot in
the registry code, and updated some docs.

## Validation Steps Performed
- Ran "runut", "runft" and "runuia" locally and confirmed results are
  the same as the main branch
- Profiled the before/after numbers in the Visual Studio profiler, for
  the numbers shown in the table

Co-authored-by: Austin Lamb <austinl@microsoft.com>
2020-12-16 10:40:30 -08:00
276 changed files with 9225 additions and 4000 deletions

View File

@@ -1,24 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="NuGet.org" value="https://api.nuget.org/v3/index.json" />
<!-- Add repositories here to the list of available repositories -->
<!-- Dependencies that we must carry because they're not on public nuget feeds right now. -->
<clear />
<!-- Dependencies that we can turn on to force override for testing purposes before uploading. -->
<!--<add key="Static Package Dependencies" value="dep\packages" />-->
<!-- Use our own NuGet Feed -->
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
<!-- Temporarily? use the feeds from our friends in MUX for Helix test stuff -->
<add key="dotnetfeed" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
<add key="dnceng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="MUX-Dependencies" value="https://pkgs.dev.azure.com/ms/microsoft-ui-xaml/_packaging/MUX-Dependencies/nuget/v3/index.json" />
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />
<add key="OpenConsole - Internal" value="https://microsoft.pkgs.visualstudio.com/_packaging/OpenConsole/nuget/v3/index.json" />-->
</packageSources>
<disabledPackageSources>
<clear />
</disabledPackageSources>
<config>
<add key="repositorypath" value=".\packages" />
</config>

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,8 @@
"/res/terminal/",
"/doc/specs/",
"/doc/cascadia/",
"/doc/user-docs/"
"/doc/user-docs/",
"/src/tools/MonarchPeasantSample/",
],
"SuffixFilters": [
".dbb",
@@ -38,5 +39,5 @@
".rec",
".err",
".xlsx"
]
]
}

View File

@@ -11,7 +11,7 @@
- Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes
2. Add matching fields to Settings.hpp
- add getters, setters, the whole drill.
- Add getters, setters, the whole drill.
3. Add to the propsheet
- We need to add it to *reading and writing* the registry from the propsheet, and *reading* the link from the propsheet. Yes, that's weird, but the propsheet is smart enough to re-use ShortcutSerialization::s_SetLinkValues, but not smart enough to do the same with RegistrySerialization.

View File

@@ -48,3 +48,17 @@ Invoke-OpenConsoleTests
```
`Invoke-OpenConsoleTests` supports a number of options, which you can enumerate by running `Invoke-OpenConsoleTests -?`.
### Debugging Tests
If you want to debug a test, you can do so by using the TAEF /waitForDebugger flag, such as:
runut *Tests.dll /name:TextBufferTests::TestInsertCharacter /waitForDebugger
Replace the test name with the one you want to debug. Then, TAEF will begin executing the test and output something like this:
TAEF: Waiting for debugger - PID <some PID> @ IP <some IP address>
You can then attach to that PID in your debugger of choice. In Visual Studio, you can use Debug -> Attach To Process, or you could use WinDbg or whatever you want.
Once the debugger attaches, the test will execute and your breakpoints will be hit.

395
doc/cascadia/AddASetting.md Normal file
View File

@@ -0,0 +1,395 @@
# Adding Settings to Windows Terminal
Adding a setting to Windows Terminal is fairly straightforward. This guide serves as a reference on how to add a setting.
## 1. Terminal Settings Model
The Terminal Settings Model (`Microsoft.Terminal.Settings.Model`) is responsible for (de)serializing and exposing settings.
### `GETSET_SETTING` macro
The `GETSET_SETTING` macro can be used to implement inheritance for your new setting and store the setting in the settings model. It takes three parameters:
- `type`: the type that the setting will be stored as
- `name`: the name of the variable for storage
- `defaultValue`: the value to use if the user does not define the setting anywhere
### Adding a Profile setting
This tutorial will add `CloseOnExitMode CloseOnExit` as a profile setting.
1. In `Profile.h`, declare/define the setting:
```c++
GETSET_SETTING(CloseOnExitMode, CloseOnExit, CloseOnExitMode::Graceful)
```
2. In `Profile.idl`, expose the setting via WinRT:
```c++
Boolean HasCloseOnExit();
void ClearCloseOnExit();
CloseOnExitMode CloseOnExit;
```
3. In `Profile.cpp`, add (de)serialization and copy logic:
```c++
// Top of file:
// - Add the serialization key
static constexpr std::string_view CloseOnExitKey{ "closeOnExit" };
// CopySettings() or Copy():
// - The setting is exposed in the Settings UI
profile->_CloseOnExit = source->_CloseOnExit;
// LayerJson():
// - get the value from the JSON
JsonUtils::GetValueForKey(json, CloseOnExitKey, _CloseOnExit);
// ToJson():
// - write the value to the JSON
JsonUtils::SetValueForKey(json, CloseOnExitKey, _CloseOnExit);
```
- If the setting is not a primitive type, in `TerminalSettingsSerializationHelpers.h` add (de)serialization logic for the accepted values:
```c++
// For enum values...
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode)
{
JSON_MAPPINGS(3) = {
pair_type{ "always", ValueType::Always },
pair_type{ "graceful", ValueType::Graceful },
pair_type{ "never", ValueType::Never },
};
};
// For enum flag values...
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::TerminalControl::CopyFormat)
{
JSON_MAPPINGS(5) = {
pair_type{ "none", AllClear },
pair_type{ "html", ValueType::HTML },
pair_type{ "rtf", ValueType::RTF },
pair_type{ "all", AllSet },
};
};
// NOTE: This is also where you can add functionality for...
// - overloaded type support (i.e. accept a bool and an enum)
// - custom (de)serialization logic (i.e. coordinates)
```
### Adding a Global setting
Follow the "adding a Profile setting" instructions above, but do it on the `GlobalAppSettings` files.
### Adding an Action
This tutorial will add the `openSettings` action.
1. In `KeyMapping.idl`, declare the action:
```c++
// Add the action to ShortcutAction
enum ShortcutAction
{
OpenSettings
}
```
2. In `ActionAndArgs.cpp`, add serialization logic:
```c++
// Top of file:
// - Add the serialization key
static constexpr std::string_view OpenSettingsKey{ "openSettings" };
// ActionKeyNamesMap:
// - map the new enum to the json key
{ OpenSettingsKey, ShortcutAction::OpenSettings },
```
3. If the action should automatically generate a name when it appears in the Command Palette...
```c++
// In ActionAndArgs.cpp GenerateName() --> GeneratedActionNames
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
// In Resources.resw for Microsoft.Terminal.Settings.Model.Lib,
// add the generated name
// NOTE: Visual Studio presents the resw file as a table.
// If you choose to edit the file with a text editor,
// the code should look something like this...
<data name="OpenSettingsCommandKey" xml:space="preserve">
<value>Open settings file</value>
</data>
```
4. If the action supports arguments...
- In `ActionArgs.idl`, declare the arguments
```c++
[default_interface] runtimeclass OpenSettingsArgs : IActionArgs
{
// this declares the "target" arg
SettingsTarget Target { get; };
};
```
- In `ActionArgs.h`, define the new runtime class
```c++
struct OpenSettingsArgs : public OpenSettingsArgsT<OpenSettingsArgs>
{
OpenSettingsArgs() = default;
// adds a getter/setter for your argument, and defines the json key
GETSET_PROPERTY(SettingsTarget, Target, SettingsTarget::SettingsFile);
static constexpr std::string_view TargetKey{ "target" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<OpenSettingsArgs>();
if (otherAsUs)
{
return otherAsUs->_Target == _Target;
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<OpenSettingsArgs>();
JsonUtils::GetValueForKey(json, TargetKey, args->_Target);
return { *args, {} };
}
IActionArgs Copy() const
{
auto copy{ winrt::make_self<OpenSettingsArgs>() };
copy->_Target = _Target;
return *copy;
}
};
```
- In `ActionArgs.cpp`, define `GenerateName()`. This is used to automatically generate a name when it appears in the Command Palette.
- In `ActionAndArgs.cpp`, add serialization logic:
```c++
// ActionKeyNamesMap --> argParsers
{ ShortcutAction::OpenSettings, OpenSettingsArgs::FromJson },
```
### Adding an Action Argument
Follow step 3 from the "adding an Action" instructions above, but modify the relevant `ActionArgs` files.
## 2. Setting Functionality
Now that the Terminal Settings Model is updated, Windows Terminal can read and write to the settings file. This section covers how to add functionality to your newly created setting.
### App-level settings
App-level settings are settings that affect the frame of Windows Terminal. Generally, these tend to be global settings. The `TerminalApp` project is responsible for presenting the frame of Windows Terminal. A few files of interest include:
- `TerminalPage`: XAML control responsible for the look and feel of Windows Terminal
- `AppLogic`: WinRT class responsible for window-related issues (i.e. the titlebar, focus mode, etc...)
Both have access to a `CascadiaSettings` object, for you to read the loaded setting and update Windows Terminal appropriately.
### Terminal-level settings
Terminal-level settings are settings that affect a shell session. Generally, these tend to be profile settings. The `TerminalApp` project is responsible for packaging this settings from the Terminal Settings Model to the terminal instance. There are two kinds of settings here:
- `IControlSettings`:
- These are settings that affect the `TerminalControl` (a XAML control that hosts a shell session).
- Examples include background image customization, interactivity behavior (i.e. selection), acrylic and font customization.
- The `TerminalControl` project has access to these settings via a saved `IControlSettings` member.
- `ICoreSettings`:
- These are settings that affect the `TerminalCore` (a lower level object that interacts with the text buffer).
- Examples include initial size, history size, and cursor customization.
- The `TerminalCore` project has access to these settings via a saved `ICoreSettings` member.
`TerminalApp` packages these settings into a `TerminalSettings : IControlSettings, ICoreSettings` object upon creating a new terminal instance. To do so, you must submit the following changes:
- Declare the setting in `IControlSettings.idl` or `ICoreSettings.idl` (whichever is relevant to your setting). If your setting is an enum setting, declare the enum here instead of in the `TerminalSettingsModel` project.
- In `TerminalSettings.h`, declare/define the setting...
```c++
// The GETSET_PROPERTY macro declares/defines a getter setter for the setting.
// Like GETSET_SETTING, it takes in a type, name, and defaultValue.
GETSET_PROPERTY(bool, UseAcrylic, false);
```
- In `TerminalSettings.cpp`...
- update `_ApplyProfileSettings` for profile settings
- update `_ApplyGlobalSettings` for global settings
- If additional processing is necessary, that would happen here. For example, `backgroundImageAlignment` is stored as a `ConvergedAlignment` in the Terminal Settings Model, but converted into XAML's separate horizontal and vertical alignment enums for packaging.
### Actions
Actions are packaged as an `ActionAndArgs` object, then handled in `TerminalApp`. To add functionality for actions...
- In the `ShortcutActionDispatch` files, dispatch an event when the action occurs...
```c++
// ShortcutActionDispatch.idl
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> OpenSettings;
// ShortcutActionDispatch.h
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
// ShortcutActionDispatch.cpp --> DoAction()
// - dispatch the appropriate event
case ShortcutAction::OpenSettings:
{
_OpenSettingsHandlers(*this, eventArgs);
break;
}
```
- In `TerminalPage` files, handle the event...
```c++
// TerminalPage.h
// - declare the handler
void _HandleOpenSettings(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
// TerminalPage.cpp --> _RegisterActionCallbacks()
// - register the handler
_actionDispatch->OpenSettings({ this, &TerminalPage::_HandleOpenSettings });
// AppActionHandlers.cpp
// - direct the function to the right place and call a helper function
void TerminalPage::_HandleOpenSettings(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
// NOTE: this if-statement can be omitted if the action does not support arguments
if (const auto& realArgs = args.ActionArgs().try_as<OpenSettingsArgs>())
{
_LaunchSettings(realArgs.Target());
args.Handled(true);
}
}
```
`AppActionHandlers` vary based on the action you want to perform. A few useful helper functions include:
- `_GetFocusedTab()`: retrieves the focused tab
- `_GetActiveControl()`: retrieves the active terminal control
- `_GetTerminalTabImpl()`: tries to cast the given tab as a `TerminalTab` (a tab that hosts a terminal instance)
## 3. Settings UI
### Exposing Enum Settings
If the new setting supports enums, you need to expose a map of the enum and the respective value in the Terminal Settings Model's `EnumMappings`:
```c++
// EnumMappings.idl
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CloseOnExitMode> CloseOnExitMode { get; };
// EnumMappings.h
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, CloseOnExitMode> CloseOnExitMode();
// EnumMappings.cpp
// - this macro leverages the json enum mapper in TerminalSettingsSerializationHelper to expose
// the mapped values across project boundaries
DEFINE_ENUM_MAP(Model::CloseOnExitMode, CloseOnExitMode);
```
### Binding and Localizing the Enum Setting
Find the page in the Settings UI that the new setting fits best in. In this example, we are adding `LaunchMode`.
1. In `Launch.idl`, expose the bindable setting...
```c++
// Expose the current value for the setting
IInspectable CurrentLaunchMode;
// Expose the list of possible values
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> LaunchModeList { get; };
```
2. In `Launch.h`, declare the bindable enum setting...
```c++
// the GETSET_BINDABLE_ENUM_SETTING macro accepts...
// - name: the name of the setting
// - enumType: the type of the setting
// - settingsModelName: how to retrieve the setting (use State() to get access to the settings model)
// - settingNameInModel: the name of the setting in the terminal settings model
GETSET_BINDABLE_ENUM_SETTING(LaunchMode, Model::LaunchMode, State().Settings().GlobalSettings, LaunchMode);
```
3. In `Launch.cpp`, populate these functions...
```c++
// Constructor (after InitializeComponent())
// the INITIALIZE_BINDABLE_ENUM_SETTING macro accepts...
// - name: the name of the setting
// - enumMappingsName: the name from the TerminalSettingsModel's EnumMappings
// - enumType: the type for the enum
// - resourceSectionAndType: prefix for the localization
// - resourceProperty: postfix for the localization
INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_LaunchMode", L"Content");
```
4. In `Resources.resw` for Microsoft.Terminal.Settings.Editor, add the localized text to expose each enum value. Use the following format: `<SettingGroup>_<SettingName><EnumValue>.Content`
- `SettingGroup`:
- `Globals` for global settings
- `Profile` for profile settings
- `SettingName`:
- the Pascal-case format for the setting type (i.e. `LaunchMode` for `"launchMode"`)
- `EnumValue`:
- the json key for the setting value, but with the first letter capitalized (i.e. `Focus` for `"focus"`)
- The resulting resw key should look something like this `Globals_LaunchModeFocus.Content`
- This is the text that will be used in your control
### Updating the UI
#### Enum Settings
Now, create a XAML control in the relevant XAML file. Use the following tips and tricks to style everything appropriately:
- Wrap the control in a `ContentPresenter` adhering to the `SettingContainerStyle` style
- Bind `SelectedItem` to the relevant `Current<Setting>` (i.e. `CurrentLaunchMode`). Ensure it's a TwoWay binding
- Bind `ItemsSource` to `<Setting>List` (i.e. `LaunchModeList`)
- Set the ItemTemplate to the `Enum<ControlType>Template` (i.e. `EnumRadioButtonTemplate` for radio buttons)
- Set the style to the appropriate one in `CommonResources.xaml`
```xml
<!--Launch Mode-->
<ContentPresenter Style="{StaticResource SettingContainerStyle}">
<muxc:RadioButtons x:Uid="Globals_LaunchMode"
SelectedItem="{x:Bind CurrentLaunchMode, Mode="TwoWay"}"
ItemsSource="{x:Bind LaunchModeList}"
ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
Style="{StaticResource RadioButtonsSettingStyle}"/>
</ContentPresenter>
```
To add any localized text, add a `x:Uid`, and access the relevant property via the Resources.resw file. For example, `Globals_LaunchMode.Header` sets the header for this control. You can also set the tooltip text like this:
`Globals_DefaultProfile.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip`.
#### Non-Enum Settings
Continue to reference `CommonResources.xaml` for appropriate styling and wrap the control with a similar `ContentPresenter`. However, instead of binding to the `Current<Setting>` and `<Setting>List`, bind directly to the setting via the state. Binding a setting like `altGrAliasing` should look something like this:
```xml
<!--AltGr Aliasing-->
<ContentPresenter Style="{StaticResource SettingContainerStyle}">
<CheckBox x:Uid="Profile_AltGrAliasing"
IsChecked="{x:Bind State.Profile.AltGrAliasing, Mode=TwoWay}"
Style="{StaticResource CheckBoxSettingStyle}"/>
</ContentPresenter>
```
#### Profile Settings
If you are specifically adding a Profile setting, in addition to the steps above, you need to make the setting observable by modifying the `Profiles` files...
```c++
// Profiles.idl --> ProfileViewModel
// - this declares the setting as observable using the type and the name of the setting
OBSERVABLE_PROJECTED_SETTING(Microsoft.Terminal.Settings.Model.CloseOnExitMode, CloseOnExit);
// Profiles.h --> ProfileViewModel
// - this defines the setting as observable off of the _profile object
OBSERVABLE_PROJECTED_SETTING(_profile, CloseOnExit);
// Profiles.h --> ProfileViewModel
// - if the setting cannot be inherited by another profile (aka missing the Clear() function), use the following macro instead:
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);
```
The `ProfilePageNavigationState` holds a `ProfileViewModel`, which wraps the `Profile` object from the Terminal Settings Model. The `ProfileViewModel` makes all of the profile settings observable.
### Actions
Actions are not yet supported in the Settings UI.

View File

@@ -93,6 +93,8 @@
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"scrollToBottom",
"scrollToTop",
"sendInput",
"setColorScheme",
"setTabColor",
@@ -369,6 +371,13 @@
"splitMode": {
"default": "duplicate",
"description": "Control how the pane splits. Only accepts \"duplicate\" which will duplicate the focused pane's profile into a new pane."
},
"size": {
"default": 0.5,
"description": "Specify how large the new pane should be, as a fraction of the current pane's size. 1.0 would be 'all of the current pane', and 0.0 is 'None of the parent'. Accepts floating point values from 0-1 (default 0.5).",
"maximum": 1,
"minimum": 0,
"type": "number"
}
}
}
@@ -649,6 +658,10 @@
"description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".",
"type": "string"
},
"startupActions": {
"description": "Sets the list of actions to apply if no command line is provided. Uses the same format as command line arguments",
"type": "string"
},
"disabledProfileSources": {
"description": "Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup.",
"items": {
@@ -922,9 +935,10 @@
},
"cursorShape": {
"default": "bar",
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"doubleUnderscore\" ( ‗ )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
"enum": [
"bar",
"doubleUnderscore",
"emptyBox",
"filledBox",
"underscore",

View File

@@ -0,0 +1,302 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-11-23
last updated: 2020-12-15
issue id: #2871
---
# Focus Pane Actions
## Abstract
Currently, the Terminal only allows users to navigate through panes
_directionally_. However, we might also want to allow a user to navigate through
panes in most recently used order ("MRU" order), or to navigate directly to a
specific pane. This spec proposes some additional actions in order to enable
these sorts of scenarios.
## Background
### Inspiration
`tmux` allows the user to navigate through panes using its `select-pane`
command. The `select-pane` command works in the following way:
```
select-pane [-DLlMmRU] [-T title] [-t target-pane]
Make pane target-pane the active pane in window target-window, or set its
style (with -P). If one of -D, -L, -R, or -U is used, respectively the
pane below, to the left, to the right, or above the target pane is used.
-l is the same as using the last-pane command.
-m and -M are used to set and clear the marked pane. There is one marked
pane at a time, setting a new marked pane clears the last. The marked pane
is the default target for -s to join-pane, swap-pane and swap-window.
```
_from `man tmux`_.
The Terminal currently allows the user to navigate through panes with the
`moveFocus` action, which only accepts a `direction` to move in.
Additionally, the Terminal allows movement between tabs with the `nextTab` and
`prevTab` actions, who move between tabs either in-order or in MRU order.
Furthermore, these actions may or may not display the "tab switcher" user
interface, based on the value of `tabSwitcherMode`.
### User Stories
* **Scenario 1**: A user who wants to be able to split the window into 4 equal
corners from the commandline. Currently this isn't possible, because the user
cannot move focus during the startup actions - `split-pane` actions always end
up splitting the current leaf in the tree of panes. (see [#5464])
* **Scenario 2**: A user who wants to quickly navigate to the previous pane they
had opened. (see [#2871])
* **Scenario 3**: A user who wants to bind a keybinding like <kbd>alt+1</kbd>,
<kbd>alt+2</kbd>, etc to immediately focus the first, second, etc. pane in a
tab. (see [#5803])
### Future Considerations
There's been talk of updating the advanced tab switcher to also display panes,
in addition to just tabs. This would allow users to navigate through the ATS
directly to a pane, and see all the panes in a tab. Currently, `tabSwitcherMode`
changes the behavior of `nextTab`, `prevTab` - should we just build the
`paneSwitcherMode` directly into the action we end up designing?
## Solution Design
Does using the pane switcher with a theoretical `focusPane(target=id)` action
even make sense? Certainly not! That's like `switchToTab(index=id)`, the user
already knows which tab they want to go to, there's no reason to pop an
ephemeral UI in front of them.
Similarly, it almost certainly doesn't make sense to display the pane switcher
while moving focus directionally. Consider moving focus with a key bound to the
arrow keys. Displaying another UI in front of them while moving focus with the
arrow keys would be confusing.
Addressing Scenario 1 is relatively easy. So long as we add any of the proposed
actions, including the existing `moveFocus` action as a subcommand that can be
passed to `wt.exe`, then the user should be able to navigate through the panes
they've created with the startup commandline, and build the tree of panes
however they see fit.
Scenario 2 is more complicated, because MRU switching is always more
complicated. Without a UI of some sort, there's no way to switch to another pane
in the MRU order without also updating the MRU order as you go. So this would
almost certainly necessitate a "pane switcher", like the tab switcher.
### Proposal A: Add next, prev to moveFocus
* `moveFocus(direction="up|down|left|right|next|prev")`
* **Pros**:
- Definitely gets the "MRU Pane Switching" scenario working
* **Cons**:
- Doesn't really address any of the other scenarios
- How will it play with pane switching in the UI?
- MRU switching without a dialog to track & display the MRU stack doesn't
really work - this only allows to the user to navigate to the most recently
used pane, or through all the panes in least-recently-used order. This is
because switching to the MRU pane _will update the MRU pane_.
❌ This proposal is no longer being considered.
### Proposal B: focusNextPane, focusPrevPane with order, useSwitcher args
```json
// Focus pane 1
// - This is sensible, no arguments here
{ "command": { "action": "focusPane", "id": 1 } },
// Focus the next MRU pane
// - Without the switcher, this can only go one pane deep in the MRU stack
// - presumably once there's a pane switcher, it would default to enabled?
{ "command": { "action": "focusNextPane", "order": "mru" } },
// Focus the prev inOrder pane
// - this seems straightforward
{ "command": { "action": "focusPrevPane", "order": "inOrder" } },
// Focus the next pane, in mru order, explicitly disable the switcher
// - The user opted in to only being able to MRU switch one deep. That's fine, that's what they want.
{ "command": { "action": "focusNextPane", "order": "mru", "useSwitcher": false} },
// Focus the prev inOrder pane, explicitly with the switcher
// - Maybe they disabled the switcher globally, but what it on for this action?
{ "command": { "action": "focusPrevPane", "order": "inOrder", "useSwitcher": true } },
```
_From [discussion in the implementation
PR](https://github.com/microsoft/terminal/pull/8183#issuecomment-729672645)_
Boiled down, that's three actions:
* `focusPane(target=id)`
* `focusNextPane(order="inOrder|mru", useSwitcher=true|false)`
* `focusPrevPane(order="inOrder|mru", useSwitcher=true|false)`
* **Pros**:
- Everything is explicit, including the option to use the pane switcher (when
available)
- Adds support for in-order pane switching
- No "conditional parameters" - where providing one argument makes other
arguments invalid or ambiguous.
* **Cons**:
- Doesn't really address any of the other scenarios
- What does the "next most-recently-used tab" even mean? How is it different
than "previous most-recently-used tab"? Semantically, these are the same
thing!
- No one's even asked for in-order pane switching. Is that a UX that even
really makes sense?
❌ This proposal is no longer being considered.
> 👉 **NOTE**: At this point, we stopped considering navigating in both MRU
> "directions", since both the next and prev MRU pane are the same thing. We're
> now using "last" to mean "the previous MRU pane".
### Proposal C: One actions, combine the args
* `moveFocus(target=id|"up|down|left|right|last")`
* **Pros**:
- Absolutely the least complicated action to author. There's only one
parameter, `target`.
- No "conditional parameters".
* **Cons**:
- How do we express this in the Settings UI? Mixed-type enums work fine for
the font weight, where each enum value has a distinct integer value it maps
to, but in this case, using `id` is entirely different from the other
directional values
❌ This proposal is no longer being considered.
### Proposal D: Two actions
* `focusPane(target=id)`
* `moveFocus(direction="up|down|left|right|last")`
* **Pros**:
- Each action does explicitly one thing.
* **Cons**:
- two actions for _similar_ behavior
- This now forks the "Direction" enum into "MoveFocusDirection" and
"ResizeDirection" (because `resizePane(last)` doesn't make any sense).
This proposal doesn't really have any special consideration for the pane
switcher UX. Neither of these actions would summon the pane switcher UX.
### Proposal E: Three actions
* `focusPane(target=id)`
* `moveFocus(direction="up|down|left|right")`
* `focusLastPane(usePaneSwitcher=false|true)`
In this design, neither `focusPane` nor `moveFocus` will summon the pane
switcher UI (even once it's added). However, the `focusLastPane` one _could_,
and subsequent keypresses could pop you through the MRU stack, while it's
visible? The pane switcher could then display the panes for the tab in MRU
order, and the user could just use the arrow keys to navigate the list if they
so choose.
* **Pros**:
- Each action does explicitly one thing.
- Design accounts for future pane switcher UX
* **Cons**:
- Three separate actions for similar behavior
❌ This proposal is no longer being considered.
### Proposal F: It's literally just tmux
_Also known as the "one action to rule them all" proposal_
`focusPane(target=id, direction="up|down|left|right|last")`
Previously, this design was avoided, because what does `focusPane(target=4,
direction=down)` do? Does it focus pane 4, or does it move focus down?
`tmux` solves this in one action by just doing both!
```
Make pane target-pane the active pane ... If one of -D, -L, -R, or -U is used,
respectively the pane below, to the left, to the right, or above the target pane
is used.
```
_from `man tmux`_.
So `focusPane(target=1, direction=up)` will attempt to focus the pane above pane
1. This action would not summon the pane switcher UX, even for
`focusPane(direction=last)`
* **Pros**:
- Fewest redundant actions
* **Cons**:
- Is this intuitive? That combining the params would do both, with `target`
happening "first"?
- Assumes that there will be a separate action added in the future for "Open
the pane switcher (with some given ordering)"
> 👉 **NOTE**: At this point, the author considered "Do we even want a separate
> action to engage the tab switcher with panes expanded?" Perhaps panes being
> visible in the tab switcher is just part fo the tab switcher's behavior. Maybe
> there shouldn't be a separate "open the tab switcher with the panes expanded
> to the pane I'm currently on, and the panes listed in MRU order" action.
❌ This proposal is no longer being considered.
## Conclusion
After much discussion as a team, we decided that **Proposal D** would be the
best option. We felt that there wasn't a need to add any extra configuration to
invoke the "pane switcher" as anything different than the "tab switcher". The
"pane switcher" should really just exist as a part of the functionality of the
advanced tab switcher, not as it's own thing.
Additionally, we concurred that the new "direction" value should be `prev`, not
`last`, for consistency's sake.
## UI/UX Design
The only real UX being added with the agreed upon design is allowing the user to
execute an action to move to the previously active pane within a single tab. No
additional UX (including the pane switcher) is being prescribed in this spec at
this time.
## Potential Issues
<table>
<tr>
<td><strong>Compatibility</strong></td>
<td>
We've only adding a single enum value to an existing enum. Since we're not
changing the meaning of any of the existing values, we do not expect any
compatibility issues there. Additionally, we're not changing the default value
of the `direction` param of the `moveFocus` action, so there are no further
compatibility concerns there. Furthermore, no additional parameters are being
added to the `moveFocus` action that would potentially give it a different
meaning.
</td>
</tr>
</table>
In the current design, there's no way to move through all the panes with a
single keybinding. For example, if a user wanted to bind <kbd>Alt+]</kbd> to
move to the "next" pane, and <kbd>Alt+[</kbd> to move to the "previous" one.
These movements would necessarily need to be in-order traversals, since there's
no way of doing multiple MRU steps.
Fortunately, no one's really asked for traversing the panes in-order, so we're
not really worried about this. Otherwise, it would maybe make sense for `last`
to be the "previous MRU pane", and reserve `next`/`prev` for in-order traversal.
[#2871]: https://github.com/microsoft/terminal/issues/2871
[#5464]: https://github.com/microsoft/terminal/issues/5464
[#5803]: https://github.com/microsoft/terminal/issues/5803

Binary file not shown.

Binary file not shown.

View File

@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
### Fonts Included
* Cascadia Code, Cascadia Mono (2009.21)
* from microsoft/cascadia-code@32f84124db1970fa5d032f0fe9019e6922961beb
* Cascadia Code, Cascadia Mono (2102.25)
* from microsoft/cascadia-code@911dc421f333e3b72b97381d16fee5b71eb48f04

View File

@@ -11,10 +11,16 @@
// - attr - the default text attribute
// Return Value:
// - constructed object
// Note: will throw exception if unable to allocate memory for text attribute storage
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr) noexcept
{
_list.push_back(TextAttributeRun(cchRowWidth, attr));
try
{
_list.emplace_back(TextAttributeRun(cchRowWidth, attr));
}
catch (...)
{
FAIL_FAST_CAUGHT_EXCEPTION();
}
_cchRowWidth = cchRowWidth;
}
@@ -25,7 +31,7 @@ ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
void ATTR_ROW::Reset(const TextAttribute attr)
{
_list.clear();
_list.push_back(TextAttributeRun(_cchRowWidth, attr));
_list.emplace_back(TextAttributeRun(_cchRowWidth, attr));
}
// Routine Description:
@@ -178,15 +184,15 @@ size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
// Routine Description:
// - Finds the hyperlink IDs present in this row and returns them
// Return value:
// - An unordered set containing the hyperlink IDs present in this row
std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
// - The hyperlink IDs present in this row
std::vector<uint16_t> ATTR_ROW::GetHyperlinks()
{
std::unordered_set<uint16_t> ids;
std::vector<uint16_t> ids;
for (const auto& run : _list)
{
if (run.GetAttributes().IsHyperlink())
{
ids.emplace(run.GetAttributes().GetHyperlinkId());
ids.emplace_back(run.GetAttributes().GetHyperlinkId());
}
}
return ids;
@@ -402,7 +408,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// The original run was 3 long. The insertion run was 1 long. We need 1 more for the
// fact that an existing piece of the run was split in half (to hold the latter half).
const size_t cNewRun = _list.size() + newAttrs.size() + 1;
std::vector<TextAttributeRun> newRun;
decltype(_list) newRun;
newRun.reserve(cNewRun);
// We will start analyzing from the beginning of our existing run.
@@ -595,8 +601,7 @@ std::vector<TextAttributeRun> ATTR_ROW::PackAttrs(const std::vector<TextAttribut
{
if (runs.empty() || runs.back().GetAttributes() != attr)
{
const TextAttributeRun run(1, attr);
runs.push_back(run);
runs.emplace_back(TextAttributeRun(1, attr));
}
else
{

View File

@@ -28,9 +28,16 @@ class ATTR_ROW final
public:
using const_iterator = typename AttrRowIterator;
ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr);
ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
noexcept;
void Reset(const TextAttribute attr);
~ATTR_ROW() = default;
ATTR_ROW(const ATTR_ROW&) = default;
ATTR_ROW& operator=(const ATTR_ROW&) = default;
ATTR_ROW(ATTR_ROW&&)
noexcept = default;
ATTR_ROW& operator=(ATTR_ROW&&) noexcept = default;
TextAttribute GetAttrByColumn(const size_t column) const;
TextAttribute GetAttrByColumn(const size_t column,
@@ -41,7 +48,7 @@ public:
size_t FindAttrIndex(const size_t index,
size_t* const pApplies) const;
std::unordered_set<uint16_t> GetHyperlinks();
std::vector<uint16_t> GetHyperlinks();
bool SetAttrToEnd(const UINT iStart, const TextAttribute attr);
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept;
@@ -63,12 +70,16 @@ public:
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
friend class AttrRowIterator;
friend class ROW;
private:
std::vector<TextAttributeRun> _list;
void Reset(const TextAttribute attr);
boost::container::small_vector<TextAttributeRun, 1> _list;
size_t _cchRowWidth;
#ifdef UNIT_TESTING
friend class AttrRowTests;
friend class CommonState;
#endif
};

View File

@@ -39,19 +39,6 @@ bool AttrRowIterator::operator!=(const AttrRowIterator& it) const noexcept
return !(*this == it);
}
AttrRowIterator& AttrRowIterator::operator++() noexcept
{
_increment(1);
return *this;
}
AttrRowIterator AttrRowIterator::operator++(int) noexcept
{
auto copy = *this;
_increment(1);
return copy;
}
AttrRowIterator& AttrRowIterator::operator+=(const ptrdiff_t& movement)
{
if (!_exceeded)
@@ -74,19 +61,6 @@ AttrRowIterator& AttrRowIterator::operator-=(const ptrdiff_t& movement)
return this->operator+=(-movement);
}
AttrRowIterator& AttrRowIterator::operator--() noexcept
{
_decrement(1);
return *this;
}
AttrRowIterator AttrRowIterator::operator--(int) noexcept
{
auto copy = *this;
_decrement(1);
return copy;
}
const TextAttribute* AttrRowIterator::operator->() const
{
THROW_HR_IF(E_BOUNDS, _exceeded);

View File

@@ -38,20 +38,38 @@ public:
bool operator==(const AttrRowIterator& it) const noexcept;
bool operator!=(const AttrRowIterator& it) const noexcept;
AttrRowIterator& operator++() noexcept;
AttrRowIterator operator++(int) noexcept;
AttrRowIterator& operator++() noexcept
{
_increment(1);
return *this;
}
AttrRowIterator operator++(int) noexcept
{
auto copy = *this;
_increment(1);
return copy;
}
AttrRowIterator& operator+=(const ptrdiff_t& movement);
AttrRowIterator& operator-=(const ptrdiff_t& movement);
AttrRowIterator& operator--() noexcept;
AttrRowIterator operator--(int) noexcept;
AttrRowIterator& operator--() noexcept
{
_decrement(1);
return *this;
}
AttrRowIterator operator--(int) noexcept
{
auto copy = *this;
_decrement(1);
return copy;
}
const TextAttribute* operator->() const;
const TextAttribute& operator*() const;
private:
std::vector<TextAttributeRun>::const_iterator _run;
boost::container::small_vector_base<TextAttributeRun>::const_iterator _run;
const ATTR_ROW* _pAttrRow;
size_t _currentAttributeIndex; // index of TextAttribute within the current TextAttributeRun
bool _exceeded;

View File

@@ -15,57 +15,14 @@
// Return Value:
// - instantiated object
// Note: will through if unable to allocate char/attribute buffers
CharRow::CharRow(size_t rowWidth, ROW* const pParent) :
_wrapForced{ false },
_doubleBytePadded{ false },
#pragma warning(push)
#pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build.
CharRow::CharRow(size_t rowWidth, ROW* const pParent) noexcept :
_data(rowWidth, value_type()),
_pParent{ FAIL_FAST_IF_NULL(pParent) }
{
}
// Routine Description:
// - Sets the wrap status for the current row
// Arguments:
// - wrapForced - True if the row ran out of space and we forced to wrap to the next row. False otherwise.
// Return Value:
// - <none>
void CharRow::SetWrapForced(const bool wrapForced) noexcept
{
_wrapForced = wrapForced;
}
// Routine Description:
// - Gets the wrap status for the current row
// Arguments:
// - <none>
// Return Value:
// - True if the row ran out of space and we were forced to wrap to the next row. False otherwise.
bool CharRow::WasWrapForced() const noexcept
{
return _wrapForced;
}
// Routine Description:
// - Sets the double byte padding for the current row
// Arguments:
// - fWrapWasForced - True if the row ran out of space for a double byte character and we padded out the row. False otherwise.
// Return Value:
// - <none>
void CharRow::SetDoubleBytePadded(const bool doubleBytePadded) noexcept
{
_doubleBytePadded = doubleBytePadded;
}
// Routine Description:
// - Gets the double byte padding status for the current row.
// Arguments:
// - <none>
// Return Value:
// - True if the row didn't have space for a double byte character and we were padded out the row. False otherwise.
bool CharRow::WasDoubleBytePadded() const noexcept
{
return _doubleBytePadded;
}
#pragma warning(pop)
// Routine Description:
// - gets the size of the row, in glyph cells
@@ -90,9 +47,6 @@ void CharRow::Reset() noexcept
{
cell.Reset();
}
_wrapForced = false;
_doubleBytePadded = false;
}
// Routine Description:
@@ -141,7 +95,7 @@ typename CharRow::const_iterator CharRow::cend() const noexcept
// - The calculated left boundary of the internal string.
size_t CharRow::MeasureLeft() const noexcept
{
std::vector<value_type>::const_iterator it = _data.cbegin();
const_iterator it = _data.cbegin();
while (it != _data.cend() && it->IsSpace())
{
++it;
@@ -155,9 +109,9 @@ size_t CharRow::MeasureLeft() const noexcept
// - <none>
// Return Value:
// - The calculated right boundary of the internal string.
size_t CharRow::MeasureRight() const noexcept
size_t CharRow::MeasureRight() const
{
std::vector<value_type>::const_reverse_iterator it = _data.crbegin();
const_reverse_iterator it = _data.crbegin();
while (it != _data.crend() && it->IsSpace())
{
++it;

View File

@@ -49,27 +49,21 @@ class CharRow final
public:
using glyph_type = typename wchar_t;
using value_type = typename CharRowCell;
using iterator = typename std::vector<value_type>::iterator;
using const_iterator = typename std::vector<value_type>::const_iterator;
using iterator = typename boost::container::small_vector_base<value_type>::iterator;
using const_iterator = typename boost::container::small_vector_base<value_type>::const_iterator;
using const_reverse_iterator = typename boost::container::small_vector_base<value_type>::const_reverse_iterator;
using reference = typename CharRowCellReference;
CharRow(size_t rowWidth, ROW* const pParent);
CharRow(size_t rowWidth, ROW* const pParent) noexcept;
void SetWrapForced(const bool wrap) noexcept;
bool WasWrapForced() const noexcept;
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept;
bool WasDoubleBytePadded() const noexcept;
size_t size() const noexcept;
void Reset() noexcept;
[[nodiscard]] HRESULT Resize(const size_t newSize) noexcept;
size_t MeasureLeft() const noexcept;
size_t MeasureRight() const noexcept;
void ClearCell(const size_t column);
size_t MeasureRight() const;
bool ContainsText() const noexcept;
const DbcsAttribute& DbcsAttrAt(const size_t column) const;
DbcsAttribute& DbcsAttrAt(const size_t column);
void ClearGlyph(const size_t column);
std::wstring GetText() const;
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const;
@@ -80,9 +74,11 @@ public:
// iterators
iterator begin() noexcept;
const_iterator cbegin() const noexcept;
const_iterator begin() const noexcept { return cbegin(); }
iterator end() noexcept;
const_iterator cend() const noexcept;
const_iterator end() const noexcept { return cend(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
@@ -91,29 +87,21 @@ public:
void UpdateParent(ROW* const pParent);
friend CharRowCellReference;
friend constexpr bool operator==(const CharRow& a, const CharRow& b) noexcept;
friend class ROW;
private:
void Reset() noexcept;
void ClearCell(const size_t column);
std::wstring GetText() const;
protected:
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
bool _wrapForced;
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
bool _doubleBytePadded;
// storage for glyph data and dbcs attributes
std::vector<value_type> _data;
boost::container::small_vector<value_type, 120> _data;
// ROW that this CharRow belongs to
ROW* _pParent;
};
constexpr bool operator==(const CharRow& a, const CharRow& b) noexcept
{
return (a._wrapForced == b._wrapForced &&
a._doubleBytePadded == b._doubleBytePadded &&
a._data == b._data);
}
template<typename InputIt1, typename InputIt2>
void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt)
{

View File

@@ -8,18 +8,6 @@
// default glyph value, used for resetting the character data portion of a cell
static constexpr wchar_t DefaultValue = UNICODE_SPACE;
CharRowCell::CharRowCell() noexcept :
_wch{ DefaultValue },
_attr{}
{
}
CharRowCell::CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept :
_wch{ wch },
_attr{ attr }
{
}
// Routine Description:
// - "erases" the glyph. really sets it back to the default "empty" value
void CharRowCell::EraseChars() noexcept

View File

@@ -17,6 +17,7 @@ Author(s):
#pragma once
#include "DbcsAttribute.hpp"
#include "unicode.hpp"
#if (defined(_M_IX86) || defined(_M_AMD64))
// currently CharRowCell's fields use 3 bytes of memory, leaving the 4th byte in unused. this leads
@@ -27,8 +28,13 @@ Author(s):
class CharRowCell final
{
public:
CharRowCell() noexcept;
CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept;
CharRowCell() noexcept = default;
CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept
:
_wch(wch),
_attr(attr)
{
}
void EraseChars() noexcept;
void Reset() noexcept;
@@ -44,8 +50,8 @@ public:
friend constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept;
private:
wchar_t _wch;
DbcsAttribute _attr;
wchar_t _wch{ UNICODE_SPACE };
DbcsAttribute _attr{};
};
#if (defined(_M_IX86) || defined(_M_AMD64))

View File

@@ -32,8 +32,8 @@ public:
}
~CharRowCellReference() = default;
CharRowCellReference(const CharRowCellReference&) = default;
CharRowCellReference(CharRowCellReference&&) = default;
CharRowCellReference(const CharRowCellReference&) noexcept = default;
CharRowCellReference(CharRowCellReference&&) noexcept = default;
void operator=(const CharRowCellReference&) = delete;
void operator=(CharRowCellReference&&) = delete;

View File

@@ -16,50 +16,17 @@
// - pParent - the text buffer that this row belongs to
// Return Value:
// - constructed object
ROW::ROW(const SHORT rowId, const short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) :
ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) noexcept :
_id{ rowId },
_rowWidth{ gsl::narrow<size_t>(rowWidth) },
_charRow{ gsl::narrow<size_t>(rowWidth), this },
_attrRow{ gsl::narrow<UINT>(rowWidth), fillAttribute },
_rowWidth{ rowWidth },
_charRow{ rowWidth, this },
_attrRow{ rowWidth, fillAttribute },
_wrapForced{ false },
_doubleBytePadded{ false },
_pParent{ pParent }
{
}
size_t ROW::size() const noexcept
{
return _rowWidth;
}
const CharRow& ROW::GetCharRow() const noexcept
{
return _charRow;
}
CharRow& ROW::GetCharRow() noexcept
{
return _charRow;
}
const ATTR_ROW& ROW::GetAttrRow() const noexcept
{
return _attrRow;
}
ATTR_ROW& ROW::GetAttrRow() noexcept
{
return _attrRow;
}
SHORT ROW::GetId() const noexcept
{
return _id;
}
void ROW::SetId(const SHORT id) noexcept
{
_id = id;
}
// Routine Description:
// - Sets all properties of the ROW to default values
// Arguments:
@@ -68,6 +35,8 @@ void ROW::SetId(const SHORT id) noexcept
// - <none>
bool ROW::Reset(const TextAttribute Attr)
{
_wrapForced = false;
_doubleBytePadded = false;
_charRow.Reset();
try
{
@@ -87,7 +56,7 @@ bool ROW::Reset(const TextAttribute Attr)
// - width - the new width, in cells
// Return Value:
// - S_OK if successful, otherwise relevant error
[[nodiscard]] HRESULT ROW::Resize(const size_t width)
[[nodiscard]] HRESULT ROW::Resize(const unsigned short width)
{
RETURN_IF_FAILED(_charRow.Resize(width));
try
@@ -113,25 +82,6 @@ void ROW::ClearColumn(const size_t column)
_charRow.ClearCell(column);
}
// Routine Description:
// - gets the text of the row as it would be shown on the screen
// Return Value:
// - wstring containing text for the row
std::wstring ROW::GetText() const
{
return _charRow.GetText();
}
RowCellIterator ROW::AsCellIter(const size_t startIndex) const
{
return AsCellIter(startIndex, size() - startIndex);
}
RowCellIterator ROW::AsCellIter(const size_t startIndex, const size_t count) const
{
return RowCellIterator(*this, startIndex, count);
}
UnicodeStorage& ROW::GetUnicodeStorage() noexcept
{
return _pParent->GetUnicodeStorage();
@@ -213,7 +163,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
{
_charRow.ClearCell(currentIndex);
_charRow.SetDoubleBytePadded(true);
SetDoubleBytePadded(true);
}
// Otherwise, copy the data given and increment the iterator.
else
@@ -231,7 +181,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
if (wrap.has_value() && fillingLastColumn)
{
// set wrap status on the row to parameter's value.
_charRow.SetWrapForced(wrap.value());
SetWrapForced(*wrap);
}
}
else

View File

@@ -24,7 +24,6 @@ Revision History:
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
#include "CharRow.hpp"
#include "RowCellIterator.hpp"
#include "UnicodeStorage.hpp"
class TextBuffer;
@@ -32,36 +31,39 @@ class TextBuffer;
class ROW final
{
public:
ROW(const SHORT rowId, const short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent)
noexcept;
size_t size() const noexcept;
size_t size() const noexcept { return _rowWidth; }
const CharRow& GetCharRow() const noexcept;
CharRow& GetCharRow() noexcept;
void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; }
bool WasWrapForced() const noexcept { return _wrapForced; }
const ATTR_ROW& GetAttrRow() const noexcept;
ATTR_ROW& GetAttrRow() noexcept;
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; }
bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; }
SHORT GetId() const noexcept;
void SetId(const SHORT id) noexcept;
const CharRow& GetCharRow() const noexcept { return _charRow; }
CharRow& GetCharRow() noexcept { return _charRow; }
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
SHORT GetId() const noexcept { return _id; }
void SetId(const SHORT id) noexcept { _id = id; }
bool Reset(const TextAttribute Attr);
[[nodiscard]] HRESULT Resize(const size_t width);
[[nodiscard]] HRESULT Resize(const unsigned short width);
void ClearColumn(const size_t column);
std::wstring GetText() const;
RowCellIterator AsCellIter(const size_t startIndex) const;
RowCellIterator AsCellIter(const size_t startIndex, const size_t count) const;
std::wstring GetText() const { return _charRow.GetText(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap = std::nullopt, std::optional<size_t> limitRight = std::nullopt);
friend bool operator==(const ROW& a, const ROW& b) noexcept;
#ifdef UNIT_TESTING
friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept;
friend class RowTests;
#endif
@@ -69,15 +71,19 @@ private:
CharRow _charRow;
ATTR_ROW _attrRow;
SHORT _id;
size_t _rowWidth;
unsigned short _rowWidth;
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
bool _wrapForced;
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
bool _doubleBytePadded;
TextBuffer* _pParent; // non ownership pointer
};
inline bool operator==(const ROW& a, const ROW& b) noexcept
#ifdef UNIT_TESTING
constexpr bool operator==(const ROW& a, const ROW& b) noexcept
{
return (a._charRow == b._charRow &&
a._attrRow == b._attrRow &&
a._rowWidth == b._rowWidth &&
a._pParent == b._pParent &&
// comparison is only used in the tests; this should suffice.
return (a._pParent == b._pParent &&
a._id == b._id);
}
#endif

View File

@@ -1,110 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "RowCellIterator.hpp"
#include "Row.hpp"
#include "../../types/inc/convert.hpp"
#include "../../types/inc/Utf16Parser.hpp"
RowCellIterator::RowCellIterator(const ROW& row, const size_t start, const size_t length) :
_row(row),
_start(start),
_length(length),
_pos(start),
_view(s_GenerateView(row, start))
{
}
RowCellIterator::operator bool() const noexcept
{
// In lieu of using start and end, this custom iterator type simply becomes bool false
// when we run out of items to iterate over.
return _pos < (_start + _length);
}
bool RowCellIterator::operator==(const RowCellIterator& it) const noexcept
{
return _row == it._row &&
_start == it._start &&
_length == it._length &&
_pos == it._pos;
}
bool RowCellIterator::operator!=(const RowCellIterator& it) const noexcept
{
return !(*this == it);
}
RowCellIterator& RowCellIterator::operator+=(const ptrdiff_t& movement) noexcept
{
_pos += movement;
return (*this);
}
RowCellIterator& RowCellIterator::operator++() noexcept
{
return this->operator+=(1);
}
RowCellIterator RowCellIterator::operator++(int) noexcept
{
auto temp(*this);
operator++();
return temp;
}
RowCellIterator RowCellIterator::operator+(const ptrdiff_t& movement) noexcept
{
auto temp(*this);
temp += movement;
return temp;
}
const OutputCellView& RowCellIterator::operator*() const noexcept
{
return _view;
}
const OutputCellView* RowCellIterator::operator->() const noexcept
{
return &_view;
}
// Routine Description:
// - Member function to update the view to the current position in the buffer with
// the data held on this object.
// Arguments:
// - <none>
// Return Value:
// - <none>
void RowCellIterator::_RefreshView()
{
_view = s_GenerateView(_row, _pos);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - view - View representing characters corresponding to a single glyph
// - attr - Color attributes to apply to the text
// Return Value:
// - Object representing the view into this cell
OutputCellView RowCellIterator::s_GenerateView(const ROW& row,
const size_t pos)
{
const auto& charRow = row.GetCharRow();
const auto glyph = charRow.GlyphAt(pos);
const auto dbcsAttr = charRow.DbcsAttrAt(pos);
const auto textAttr = row.GetAttrRow().GetAttrByColumn(pos);
const auto behavior = TextAttributeBehavior::Stored;
return OutputCellView(glyph, dbcsAttr, textAttr, behavior);
}

View File

@@ -1,59 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- RowCellIterator.hpp
Abstract:
- Read-only view into cells already in the input buffer.
- This is done for performance reasons (avoid heap allocs and copies).
Author:
- Michael Niksa (MiNiksa) 12-Oct-2018
--*/
#pragma once
#include "OutputCellView.hpp"
class ROW;
class RowCellIterator final
{
public:
using iterator_category = std::forward_iterator_tag;
using value_type = OutputCellView;
using difference_type = ptrdiff_t;
using pointer = OutputCellView*;
using reference = OutputCellView&;
RowCellIterator(const ROW& row, const size_t start, const size_t length);
~RowCellIterator() = default;
RowCellIterator& operator=(const RowCellIterator& it) = delete;
operator bool() const noexcept;
bool operator==(const RowCellIterator& it) const noexcept;
bool operator!=(const RowCellIterator& it) const noexcept;
RowCellIterator& operator+=(const ptrdiff_t& movement) noexcept;
RowCellIterator& operator++() noexcept;
RowCellIterator operator++(int) noexcept;
RowCellIterator operator+(const ptrdiff_t& movement) noexcept;
const OutputCellView& operator*() const noexcept;
const OutputCellView* operator->() const noexcept;
private:
const ROW& _row;
const size_t _start;
const size_t _length;
size_t _pos;
OutputCellView _view;
void _RefreshView();
static OutputCellView s_GenerateView(const ROW& row, const size_t pos);
};

View File

@@ -1,47 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "TextAttributeRun.hpp"
TextAttributeRun::TextAttributeRun() noexcept :
_cchLength(0)
{
SetAttributes(TextAttribute(0));
}
TextAttributeRun::TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept :
_cchLength(cchLength)
{
SetAttributes(attr);
}
size_t TextAttributeRun::GetLength() const noexcept
{
return _cchLength;
}
void TextAttributeRun::SetLength(const size_t cchLength) noexcept
{
_cchLength = cchLength;
}
void TextAttributeRun::IncrementLength() noexcept
{
_cchLength++;
}
void TextAttributeRun::DecrementLength() noexcept
{
_cchLength--;
}
const TextAttribute& TextAttributeRun::GetAttributes() const noexcept
{
return _attributes;
}
void TextAttributeRun::SetAttributes(const TextAttribute textAttribute) noexcept
{
_attributes = textAttribute;
}

View File

@@ -25,20 +25,24 @@ Revision History:
class TextAttributeRun final
{
public:
TextAttributeRun() noexcept;
TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept;
TextAttributeRun() = default;
TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept :
_cchLength(gsl::narrow<unsigned int>(cchLength))
{
SetAttributes(attr);
}
size_t GetLength() const noexcept;
void SetLength(const size_t cchLength) noexcept;
void IncrementLength() noexcept;
void DecrementLength() noexcept;
size_t GetLength() const noexcept { return _cchLength; }
void SetLength(const size_t cchLength) noexcept { _cchLength = gsl::narrow<unsigned int>(cchLength); }
void IncrementLength() noexcept { _cchLength++; }
void DecrementLength() noexcept { _cchLength--; }
const TextAttribute& GetAttributes() const noexcept;
void SetAttributes(const TextAttribute textAttribute) noexcept;
const TextAttribute& GetAttributes() const noexcept { return _attributes; }
void SetAttributes(const TextAttribute textAttribute) noexcept { _attributes = textAttribute; }
private:
size_t _cchLength;
TextAttribute _attributes;
unsigned int _cchLength{ 0 };
TextAttribute _attributes{ 0 };
#ifdef UNIT_TESTING
friend class AttrRowTests;

View File

@@ -6,7 +6,7 @@
<RootNamespace>bufferout</RootNamespace>
<ProjectName>BufferOut</ProjectName>
<TargetName>ConBufferOut</TargetName>
<ConfigurationType>StaticLibrary</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
@@ -18,11 +18,9 @@
<ClCompile Include="..\OutputCellRect.cpp" />
<ClCompile Include="..\OutputCellView.cpp" />
<ClCompile Include="..\Row.cpp" />
<ClCompile Include="..\RowCellIterator.cpp" />
<ClCompile Include="..\search.cpp" />
<ClCompile Include="..\TextColor.cpp" />
<ClCompile Include="..\TextAttribute.cpp" />
<ClCompile Include="..\TextAttributeRun.cpp" />
<ClCompile Include="..\textBuffer.cpp" />
<ClCompile Include="..\textBufferCellIterator.cpp" />
<ClCompile Include="..\textBufferTextIterator.cpp" />
@@ -45,7 +43,6 @@
<ClInclude Include="..\OutputCellRect.hpp" />
<ClInclude Include="..\OutputCellView.hpp" />
<ClInclude Include="..\Row.hpp" />
<ClInclude Include="..\RowCellIterator.hpp" />
<ClInclude Include="..\search.h" />
<ClInclude Include="..\TextColor.h" />
<ClInclude Include="..\TextAttribute.h" />

View File

@@ -37,10 +37,8 @@ SOURCES= \
..\OutputCellRect.cpp \
..\OutputCellView.cpp \
..\Row.cpp \
..\RowCellIterator.cpp \
..\TextColor.cpp \
..\TextAttribute.cpp \
..\TextAttributeRun.cpp \
..\textBuffer.cpp \
..\textBufferCellIterator.cpp \
..\textBufferTextIterator.cpp \

View File

@@ -42,6 +42,7 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
_currentPatternId{ 0 }
{
// initialize ROWs
_storage.reserve(static_cast<size_t>(screenBufferSize.Y));
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
{
_storage.emplace_back(static_cast<SHORT>(i), screenBufferSize.X, _currentAttributes, this);
@@ -253,7 +254,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// Erase previous character into an N type.
try
{
prevRow.GetCharRow().ClearCell(coordPrevPosition.X);
prevRow.ClearColumn(coordPrevPosition.X);
}
catch (...)
{
@@ -295,8 +296,8 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
if (GetCursor().GetPosition().X == sBufferWidth - 1)
{
// set that we're wrapping for double byte reasons
CharRow& charRow = GetRowByOffset(GetCursor().GetPosition().Y).GetCharRow();
charRow.SetDoubleBytePadded(true);
auto& row = GetRowByOffset(GetCursor().GetPosition().Y);
row.SetDoubleBytePadded(true);
// then move the cursor forward and onto the next row
fSuccess = IncrementCursor();
@@ -479,7 +480,7 @@ void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet)
const UINT uiCurrentRowOffset = GetCursor().GetPosition().Y;
// Set the wrap status as appropriate
GetRowByOffset(uiCurrentRowOffset).GetCharRow().SetWrapForced(fSet);
GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet);
}
//Routine Description:
@@ -809,8 +810,7 @@ void TextBuffer::Reset()
for (auto& row : _storage)
{
row.GetCharRow().Reset();
row.GetAttrRow().Reset(attr);
row.Reset(attr);
}
}
@@ -837,11 +837,10 @@ void TextBuffer::Reset()
const SHORT TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y;
// rotate rows until the top row is at index 0
const ROW& newTopRow = _storage.at(TopRowIndex);
while (&newTopRow != &_storage.front())
for (int i = 0; i < TopRowIndex; i++)
{
_storage.push_back(std::move(_storage.front()));
_storage.pop_front();
_storage.emplace_back(std::move(_storage.front()));
_storage.erase(_storage.begin());
}
_SetFirstRowIndex(0);
@@ -1230,9 +1229,15 @@ void TextBuffer::_PruneHyperlinks()
// If the buffer does not contain the same reference, we can remove that hyperlink from our map
// This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around
// Get all the hyperlink references in the row we're erasing
auto firstRowRefs = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
if (!firstRowRefs.empty())
const auto hyperlinks = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
if (!hyperlinks.empty())
{
// Move to unordered set so we can use hashed lookup of IDs instead of linear search.
// Only make it an unordered set now because set always heap allocates but vector
// doesn't when the set is empty (saving an allocation in the common case of no links.)
std::unordered_set<uint16_t> firstRowRefs{ hyperlinks.cbegin(), hyperlinks.cend() };
const auto total = TotalRowCount();
// Loop through all the rows in the buffer except the first row -
// we have found all hyperlink references in the first row and put them in refs,
@@ -1254,12 +1259,12 @@ void TextBuffer::_PruneHyperlinks()
break;
}
}
}
// Now delete obsolete references from our map
for (auto hyperlinkReference : firstRowRefs)
{
RemoveHyperlinkFromMap(hyperlinkReference);
// Now delete obsolete references from our map
for (auto hyperlinkReference : firstRowRefs)
{
RemoveHyperlinkFromMap(hyperlinkReference);
}
}
}
@@ -1582,7 +1587,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
}
// We apply formatting to rows if the row was NOT wrapped or formatting of wrapped rows is allowed
const bool shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).GetCharRow().WasWrapForced();
const bool shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).WasWrapForced();
if (trimTrailingWhitespace)
{
@@ -2063,7 +2068,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// included.)
// As such, adjust the "right" to be the width of the row
// to capture all these spaces
if (charRow.WasWrapForced())
if (row.WasWrapForced())
{
iRight = cOldColsTotal;
@@ -2072,7 +2077,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// piece of padding because of a double byte LEADING
// character, then remove one from the "right" to
// leave this padding out of the copy process.
if (charRow.WasDoubleBytePadded())
if (row.WasDoubleBytePadded())
{
iRight--;
}
@@ -2136,7 +2141,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Only do so if we were not forced to wrap. If we did
// force a word wrap, then the existing line break was
// only because we ran out of space.
if (iRight < cOldColsTotal && !charRow.WasWrapForced())
if (iRight < cOldColsTotal && !row.WasWrapForced())
{
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
@@ -2180,7 +2185,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const COORD coordNewCursor = newCursor.GetPosition();
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
{
if (newBuffer.GetRowByOffset(gsl::narrow_cast<size_t>(coordNewCursor.Y) - 1).GetCharRow().WasWrapForced())
if (newBuffer.GetRowByOffset(gsl::narrow_cast<size_t>(coordNewCursor.Y) - 1).WasWrapForced())
{
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
}
@@ -2213,7 +2218,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
// because the cursor is already on the next line
if (newBuffer.GetRowByOffset(cNewLastChar.Y).GetCharRow().WasWrapForced())
if (newBuffer.GetRowByOffset(cNewLastChar.Y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
@@ -2221,7 +2226,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
// old buffer will be one more than in this buffer, so new need one LESS.
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).GetCharRow().WasWrapForced())
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
@@ -2410,8 +2415,8 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
// all the text into one string and find the patterns in that string
for (auto i = firstRow; i <= lastRow; ++i)
{
auto row = GetRowByOffset(i);
concatAll += row.GetCharRow().GetText();
auto& row = GetRowByOffset(i);
concatAll += row.GetText();
}
// for each pattern we know of, iterate through the string

View File

@@ -49,6 +49,8 @@ filling in the last row, and updating the screen.
#pragma once
#include <vector>
#include "cursor.h"
#include "Row.hpp"
#include "TextAttribute.hpp"
@@ -190,7 +192,7 @@ public:
private:
void _UpdateSize();
Microsoft::Console::Types::Viewport _size;
std::deque<ROW> _storage;
std::vector<ROW> _storage;
Cursor _cursor;
SHORT _firstRow; // indexes top row (not necessarily 0)

View File

@@ -3,14 +3,9 @@
#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"
#include "../../../inc/consoletaeftemplates.hpp"
#include "CommonState.hpp"
#include "globals.h"
#include "../buffer/out/textBuffer.hpp"
#include "input.h"
#include "../textBuffer.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
@@ -170,64 +165,54 @@ class AttrRowTests
_Inout_ std::unique_ptr<TextAttributeRun[]>& outAttrRun,
_Out_ size_t* const cOutAttrRun)
{
NTSTATUS status = STATUS_SUCCESS;
RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, cRowLength == 0);
if (cRowLength == 0)
// first count up the deltas in the array
size_t cDeltas = 1;
const TextAttribute* pPrevAttr = &rgAttrs[0];
for (size_t i = 1; i < cRowLength; i++)
{
status = STATUS_BUFFER_TOO_SMALL;
}
const TextAttribute* pCurAttr = &rgAttrs[i];
if (NT_SUCCESS(status))
{
// first count up the deltas in the array
size_t cDeltas = 1;
const TextAttribute* pPrevAttr = &rgAttrs[0];
for (size_t i = 1; i < cRowLength; i++)
if (*pCurAttr != *pPrevAttr)
{
const TextAttribute* pCurAttr = &rgAttrs[i];
if (*pCurAttr != *pPrevAttr)
{
cDeltas++;
}
pPrevAttr = pCurAttr;
cDeltas++;
}
// This whole situation was too complicated with a one off holder for one row run
// new method:
// delete the old buffer
// make a new buffer, one run + one run for each change
// set the values for each run one run index at a time
pPrevAttr = pCurAttr;
}
std::unique_ptr<TextAttributeRun[]> attrRun = std::make_unique<TextAttributeRun[]>(cDeltas);
status = NT_TESTNULL(attrRun.get());
if (NT_SUCCESS(status))
// This whole situation was too complicated with a one off holder for one row run
// new method:
// delete the old buffer
// make a new buffer, one run + one run for each change
// set the values for each run one run index at a time
std::unique_ptr<TextAttributeRun[]> attrRun = std::make_unique<TextAttributeRun[]>(cDeltas);
RETURN_HR_IF_NULL(E_OUTOFMEMORY, attrRun);
TextAttributeRun* pCurrentRun = attrRun.get();
pCurrentRun->SetAttributes(rgAttrs[0]);
pCurrentRun->SetLength(1);
for (size_t i = 1; i < cRowLength; i++)
{
if (pCurrentRun->GetAttributes() == rgAttrs[i])
{
TextAttributeRun* pCurrentRun = attrRun.get();
pCurrentRun->SetAttributes(rgAttrs[0]);
pCurrentRun->SetLength(pCurrentRun->GetLength() + 1);
}
else
{
pCurrentRun++;
pCurrentRun->SetAttributes(rgAttrs[i]);
pCurrentRun->SetLength(1);
for (size_t i = 1; i < cRowLength; i++)
{
if (pCurrentRun->GetAttributes() == rgAttrs[i])
{
pCurrentRun->SetLength(pCurrentRun->GetLength() + 1);
}
else
{
pCurrentRun++;
pCurrentRun->SetAttributes(rgAttrs[i]);
pCurrentRun->SetLength(1);
}
}
attrRun.swap(outAttrRun);
*cOutAttrRun = cDeltas;
}
}
attrRun.swap(outAttrRun);
*cOutAttrRun = cDeltas;
return HRESULT_FROM_NT(status);
return S_OK;
}
NoThrowString LogRunElement(_In_ TextAttributeRun& run)
@@ -235,6 +220,24 @@ class AttrRowTests
return NoThrowString().Format(L"%wc%d", run.GetAttributes().GetLegacyAttributes(), run.GetLength());
}
void LogChain(_In_ PCWSTR pwszPrefix,
boost::container::small_vector_base<TextAttributeRun>& chain)
{
NoThrowString str(pwszPrefix);
if (chain.size() > 0)
{
str.Append(LogRunElement(chain[0]));
for (size_t i = 1; i < chain.size(); i++)
{
str.AppendFormat(L"->%s", (const wchar_t*)(LogRunElement(chain[i])));
}
}
Log::Comment(str);
}
void LogChain(_In_ PCWSTR pwszPrefix,
std::vector<TextAttributeRun>& chain)
{
@@ -707,10 +710,6 @@ class AttrRowTests
TEST_METHOD(TestResize)
{
CommonState state;
state.PrepareGlobalFont();
state.PrepareGlobalScreenBuffer();
pSingle->Resize(240);
pChain->Resize(240);
@@ -728,8 +727,5 @@ class AttrRowTests
VERIFY_THROWS_SPECIFIC(pSingle->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
VERIFY_THROWS_SPECIFIC(pChain->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
state.CleanupGlobalScreenBuffer();
state.CleanupGlobalFont();
}
};

View File

@@ -0,0 +1,856 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"
#include "../textBuffer.hpp"
#include "../../renderer/inc/DummyRenderTarget.hpp"
#include "../../types/inc/Utf16Parser.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include <IDataSource.h>
template<>
class WEX::TestExecution::VerifyOutputTraits<wchar_t>
{
public:
static WEX::Common::NoThrowString ToString(const wchar_t& wch)
{
return WEX::Common::NoThrowString().Format(L"'%c'", wch);
}
};
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
namespace
{
struct TestRow
{
std::wstring_view text;
bool wrap;
};
struct TestBuffer
{
COORD size;
std::vector<TestRow> rows;
COORD cursor;
};
struct TestCase
{
std::wstring_view name;
std::vector<TestBuffer> buffers;
};
static constexpr auto true_due_to_exact_wrap_bug{ true };
static const TestCase testCases[] = {
TestCase{
L"No reflow required",
{
TestBuffer{
{ 6, 5 },
{
{ L"AB ", false },
{ L"$ ", false },
{ L"CD ", false },
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"AB ", false },
{ L"$ ", false },
{ L"CD ", false },
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
TestBuffer{
{ 4, 5 },
{
{ L"AB ", false },
{ L"$ ", false },
{ L"CD ", false },
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
},
},
TestCase{
L"SBCS, cursor remains in buffer, no circling, no original wrap",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line.
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor on $
},
TestBuffer{
{ 6, 5 }, // grow width back to original
{
{ L"ABCDEF", true_due_to_exact_wrap_bug },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
TestBuffer{
{ 7, 5 }, // grow width wider than original
{
{ L"ABCDEF$", true_due_to_exact_wrap_bug }, // EXACT WRAP BUG: $ should be alone on next line
{ L" ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 6, 0 } // cursor on $
},
},
},
TestCase{
L"SBCS, cursor remains in buffer, no circling, with original wrap",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", true },
{ L"G$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"FG$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
},
TestBuffer{
{ 6, 5 }, // grow width back to original
{
{ L"ABCDEF", true },
{ L"G$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor on $
},
TestBuffer{
{ 7, 5 }, // grow width wider than original
{
{ L"ABCDEFG", true },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
},
},
TestCase{
L"SBCS line padded with spaces (to wrap)",
{
TestBuffer{
{ 6, 5 },
{
{ L"AB ", true }, // AB $ CD is one long wrapped line
{ L"$ ", true },
{ L"CD ", false },
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
TestBuffer{
{ 7, 5 }, // reduce width by 1
{
{ L"AB $", true },
{ L" CD", true_due_to_exact_wrap_bug },
{ L" ", false },
{ L"EFG ", false },
{ L" ", false },
},
{ 6, 0 } // cursor on $
},
TestBuffer{
{ 8, 5 },
{
{ L"AB $ ", true },
{ L" CD ", false }, // Goes to false because we hit the end of ..CD
{ L"EFG ", false }, // [BUG] EFG moves up due to exact wrap bug above
{ L" ", false },
{ L" ", false },
},
{ 6, 0 } // cursor on $
},
},
},
TestCase{
L"DBCS, cursor remains in buffer, no circling, with original wrap",
{
TestBuffer{
{ 6, 5 },
{
//--0123456--
{ L"カタカ", true }, // KA TA KA
{ L"ナ$ ", false }, // NA
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
//--012345--
{ L"カタ ", true }, // KA TA [FORCED SPACER]
{ L"カナ$", true_due_to_exact_wrap_bug }, // KA NA
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 4, 1 } // cursor on $
},
TestBuffer{
{ 6, 5 }, // grow width back to original
{
//--0123456--
{ L"カタカ", true }, // KA TA KA
{ L"ナ$ ", false }, // NA
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
},
TestBuffer{
{ 7, 5 }, // grow width wider than original (by one; no visible change!)
{
//--0123456--
{ L"カタカ ", true }, // KA TA KA [FORCED SPACER]
{ L"ナ$ ", false }, // NA
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
},
TestBuffer{
{ 8, 5 }, // grow width enough to fit second DBCS
{
//--01234567--
{ L"カタカナ", true }, // KA TA KA NA
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
},
},
},
TestCase{
L"SBCS, cursor remains in buffer, with circling, no original wrap",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
{ L"$ ", false },
{ L"GHIJKL", false },
{ L"MNOPQR", false },
{ L"STUVWX", false },
},
{ 0, 1 } // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"F$ ", false },
{ L"GHIJK", true }, // [BUG] We should see GHIJK\n L\n MNOPQ\n R\n
{ L"LMNOP", true }, // The wrapping here is irregular
{ L"QRSTU", true },
{ L"VWX ", false },
},
{ 1, 1 } // [BUG] cursor moves to 1,1 instead of sticking with the $
},
TestBuffer{
{ 6, 5 }, // going back to 6,5, the data lost has been destroyed
{
//{ L"F$ ", false }, // [BUG] this line is erroneously destroyed too!
{ L"GHIJKL", true },
{ L"MNOPQR", true },
{ L"STUVWX", true },
{ L" ", false },
{ L" ", false }, // [BUG] this line is added
},
{ 1, 1 }, // [BUG] cursor does not follow [H], it sticks at 1,1
},
TestBuffer{
{ 7, 5 }, // a number of errors are carried forward from the previous buffer
{
{ L"GHIJKLM", true },
{ L"NOPQRST", true },
{ L"UVWX ", false }, // [BUG] This line loses wrap for some reason
{ L" ", false },
{ L" ", false },
},
{ 0, 1 }, // [BUG] The cursor moves to 0, 1 now, sticking with the [N] from before
},
},
},
TestCase{
// The cursor is not found during character insertion.
// Instead, it is found off the right edge of the text. This triggers
// a separate cursor found codepath in the original algorithm.
L"SBCS, cursor off rightmost char in non-wrapped line",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor *after* $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line.
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor follows space after $ to next line
},
},
},
TestCase{
L"SBCS, cursor off rightmost char in wrapped line, which is then pushed off bottom",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", true },
{ L"GHIJKL", true },
{ L"MNOPQR", true },
{ L"STUVWX", true },
{ L"YZ0 $ ", false },
},
{ 5, 4 } // cursor *after* $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"FGHIJ", true },
{ L"KLMNO", true },
{ L"PQRST", true },
{ L"UVWXY", true },
{ L"Z0 $ ", false },
},
{ 4, 4 } // cursor follows space after $ to newly introduced bottom line
},
},
},
TestCase{
L"SBCS, cursor off in space to far right of text (end of buffer content)",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 5, 1 } // cursor in space far after $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", true }, // [BUG] This line is marked wrapped, and I do not know why
// v cursor
{ L" ", false },
// ^ cursor
{ L" ", false },
{ L" ", false },
},
{ 1, 2 } // cursor stays same linear distance from $
},
TestBuffer{
{ 6, 5 }, // grow back to original size
{
{ L"ABCDEF", true_due_to_exact_wrap_bug },
// v cursor [BUG] cursor does not retain linear distance from $
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 4, 1 } // cursor stays same linear distance from $
},
},
},
TestCase{
L"SBCS, cursor off in space to far right of text (middle of buffer content)",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L"BLAH ", false },
{ L"BLAH ", false },
{ L" ", false },
},
{ 5, 1 } // cursor in space far after $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", false },
{ L"BLAH ", false },
{ L"BLAH ", true }, // [BUG] this line wraps, no idea why
// v cursor [BUG] cursor erroneously moved to end of all content
{ L" ", false },
// ^ cursor
},
{ 0, 4 } },
TestBuffer{
{ 6, 5 }, // grow back to original size
{
{ L"ABCDEF", true },
{ L"$ ", false },
{ L"BLAH ", false },
// v cursor [BUG] cursor is pulled up to previous line because it was marked wrapped
{ L"BLAH ", false },
// ^ cursor
{ L" ", false },
},
{ 5, 3 } },
},
},
TestCase{
// Shrinking the buffer this much forces a multi-line wrap before the cursor
L"SBCS, cursor off in space to far right of text (end of buffer content), aggressive shrink",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 5, 1 } // cursor in space far after $
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L"CD", true },
{ L"EF", true },
{ L"$ ", true },
{ L" ", true },
// v cursor
{ L" ", false },
// ^ cursor
},
{ 1, 4 } },
},
},
TestCase{
L"SBCS, cursor off in space to far right of text (end of buffer content), fully wrapped, aggressive shrink",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", true },
// v cursor
{ L"$ ", true },
// ^ cursor
{ L" ", true },
{ L" ", true },
{ L" ", true },
},
{ 5, 1 } // cursor in space far after $
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L"EF", true },
{ L"$ ", true },
{ L" ", true },
{ L" ", true },
// v cursor [BUG] cursor does not maintain linear distance from $
{ L" ", false },
// ^ cursor
},
{ 1, 4 } },
},
},
TestCase{
L"SBCS, cursor off in space to far right of text (middle of buffer content), fully wrapped, aggressive shrink",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", true },
// v cursor
{ L"$ ", true },
// ^ cursor
{ L" ", true },
{ L" ", true },
{ L" Q", true },
},
{ 5, 1 } // cursor in space far after $
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L" ", true },
{ L" ", true },
{ L" ", true },
{ L" Q", true },
// v cursor [BUG] cursor jumps to end of world
{ L" ", false }, // POTENTIAL [BUG] a whole new blank line is added for the cursor
// ^ cursor
},
{ 1, 4 } },
},
},
TestCase{
L"SBCS, cursor off in space to far right of text (middle of buffer content), partially wrapped, aggressive shrink",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", true },
{ L" Q", true },
},
{ 5, 1 } // cursor in space far after $
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L" ", true },
{ L" ", true },
{ L" ", true },
{ L" Q", true },
// v cursor [BUG] cursor jumps to different place than fully wrapped case
{ L" ", false },
// ^ cursor
},
{ 0, 4 } },
},
},
TestCase{
// This triggers the cursor being walked forward w/ newlines to maintain
// distance from the last char in the buffer
L"SBCS, cursor at end of buffer, otherwise same as previous test",
{
TestBuffer{
{ 6, 5 },
{
{ L"ABCDEF", false },
{ L"$ ", false },
{ L" Q", true },
{ L" ", true },
// v cursor
{ L" ", true },
// ^ cursor
},
{ 5, 4 } // cursor at end of buffer
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L" ", true },
{ L" ", true },
{ L" Q", true },
{ L" ", false },
// v cursor [BUG] cursor loses linear distance from Q; is this important?
{ L" ", false },
// ^ cursor
},
{ 0, 4 } },
},
},
};
#pragma region TAEF hookup for the test case array above
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
{
HRESULT RuntimeClassInitialize(const size_t index)
{
_index = index;
return S_OK;
}
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
LONG index{ 0 };
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
*ppData = safeArray;
return S_OK;
}
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
{
*ppMetadataNames = nullptr;
return S_FALSE;
}
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
*ppData = nullptr;
return S_FALSE;
}
STDMETHODIMP GetName(BSTR* ppszRowName) override
{
*ppszRowName = nullptr;
return S_FALSE;
}
private:
size_t _index;
};
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
{
STDMETHODIMP Advance(IDataRow** ppDataRow) override
{
if (_index < std::extent<decltype(testCases)>::value)
{
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
}
else
{
*ppDataRow = nullptr;
}
return S_OK;
}
STDMETHODIMP Reset() override
{
_index = 0;
return S_OK;
}
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
{
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
LONG index{ 0 };
auto dataNameBstr{ wil::make_bstr(L"index") };
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
*names = safeArray;
return S_OK;
}
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
{
*type = nullptr;
return S_OK;
}
private:
size_t _index{ 0 };
};
#pragma endregion
}
extern "C" HRESULT __declspec(dllexport) __cdecl ReflowTestDataSource(IDataSource** ppDataSource, void*)
{
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
return source.CopyTo(ppDataSource);
}
class ReflowTests
{
TEST_CLASS(ReflowTests);
static DummyRenderTarget target;
static std::unique_ptr<TextBuffer> _textBufferFromTestBuffer(const TestBuffer& testBuffer)
{
auto buffer = std::make_unique<TextBuffer>(testBuffer.size, TextAttribute{ 0x7 }, 0, target);
size_t i{};
for (const auto& testRow : testBuffer.rows)
{
auto& row{ buffer->GetRowByOffset(i) };
auto& charRow{ row.GetCharRow() };
row.SetWrapForced(testRow.wrap);
size_t j{};
for (auto it{ charRow.begin() }; it != charRow.end(); ++it)
{
// Yes, we're about to manually create a buffer. It is unpleasant.
const auto ch{ til::at(testRow.text, j) };
it->Char() = ch;
if (IsGlyphFullWidth(ch))
{
it->DbcsAttr().SetLeading();
it++;
it->Char() = ch;
it->DbcsAttr().SetTrailing();
}
else
{
it->DbcsAttr().SetSingle();
}
j++;
}
i++;
}
buffer->GetCursor().SetPosition(testBuffer.cursor);
return buffer;
}
static std::unique_ptr<TextBuffer> _textBufferByReflowingTextBuffer(TextBuffer& originalBuffer, const COORD newSize)
{
auto buffer = std::make_unique<TextBuffer>(newSize, TextAttribute{ 0x7 }, 0, target);
TextBuffer::Reflow(originalBuffer, *buffer, std::nullopt, std::nullopt);
return buffer;
}
static void _compareTextBufferAgainstTestBuffer(const TextBuffer& buffer, const TestBuffer& testBuffer)
{
VERIFY_ARE_EQUAL(testBuffer.cursor, buffer.GetCursor().GetPosition());
VERIFY_ARE_EQUAL(testBuffer.size, buffer.GetSize().Dimensions());
size_t i{};
for (const auto& testRow : testBuffer.rows)
{
NoThrowString indexString;
const auto& row{ buffer.GetRowByOffset(i) };
const auto& charRow{ row.GetCharRow() };
indexString.Format(L"[Row %d]", i);
VERIFY_ARE_EQUAL(testRow.wrap, row.WasWrapForced(), indexString);
size_t j{};
for (auto it{ charRow.begin() }; it != charRow.end(); ++it)
{
indexString.Format(L"[Cell %d, %d; Text line index %d]", it - charRow.begin(), i, j);
// Yes, we're about to manually create a buffer. It is unpleasant.
const auto ch{ til::at(testRow.text, j) };
if (IsGlyphFullWidth(ch))
{
// Char is full width in test buffer, so
// ensure that real buffer is LEAD, TRAIL (ch)
VERIFY_IS_TRUE(it->DbcsAttr().IsLeading(), indexString);
VERIFY_ARE_EQUAL(ch, it->Char(), indexString);
it++;
VERIFY_IS_TRUE(it->DbcsAttr().IsTrailing(), indexString);
}
else
{
VERIFY_IS_TRUE(it->DbcsAttr().IsSingle(), indexString);
}
VERIFY_ARE_EQUAL(ch, it->Char(), indexString);
j++;
}
i++;
}
}
TEST_METHOD(TestReflowCases)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"DataSource", L"Export:ReflowTestDataSource")
END_TEST_METHOD_PROPERTIES()
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
WEX::TestExecution::SetVerifyOutput verifyOutputScope{ WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures };
unsigned int i{};
TestData::TryGetValue(L"index", i); // index is produced by the ArrayIndexTaefAdapterSource above
const auto& testCase{ testCases[i] };
Log::Comment(NoThrowString().Format(L"[%zu.0] Test case \"%.*s\"", i, testCase.name.size(), testCase.name.data()));
// Create initial text buffer from Buffer 0
auto textBuffer{ _textBufferFromTestBuffer(testCase.buffers.front()) };
for (size_t bufferIndex{ 1 }; bufferIndex < testCase.buffers.size(); ++bufferIndex)
{
const auto& testBuffer{ til::at(testCase.buffers, bufferIndex) };
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.X, testBuffer.size.Y));
auto newBuffer{ _textBufferByReflowingTextBuffer(*textBuffer, testBuffer.size) };
// All future operations are based on the new buffer
std::swap(textBuffer, newBuffer);
_compareTextBufferAgainstTestBuffer(*textBuffer, testBuffer);
}
}
};
DummyRenderTarget ReflowTests::target{};

View File

@@ -10,6 +10,8 @@
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="AttrRowTests.cpp" />
<ClCompile Include="ReflowTests.cpp" />
<ClCompile Include="TextColorTests.cpp" />
<ClCompile Include="TextAttributeTests.cpp" />
<ClCompile Include="UnicodeStorageTests.cpp" />
@@ -18,6 +20,9 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\types\lib\types.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
</ProjectReference>
<ProjectReference Include="..\lib\bufferout.vcxproj">
<Project>{0cf235bd-2da0-407e-90ee-c467e8bbc714}</Project>
</ProjectReference>

View File

@@ -14,12 +14,15 @@ DLLDEF =
SOURCES = \
$(SOURCES) \
AttrRowTests.cpp \
ReflowTests.cpp \
TextColorTests.cpp \
TextAttributeTests.cpp \
DefaultResource.rc \
TARGETLIBS = \
$(CONSOLE_OBJ_PATH)\buffer\out\lib\$(O)\ConBufferOut.lib \
$(CONSOLE_OBJ_PATH)\types\lib\$(O)\ConTypes.lib \
$(TARGETLIBS) \
# -------------------------------------

View File

@@ -3,8 +3,7 @@
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\wap-common.build.pre.props" />
<PropertyGroup Label="Configuration">
<!-- This is necessary so the build system doesn't think we're a .NET project... -->
<TargetRuntime>Native</TargetRuntime>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
<!--
@@ -17,6 +16,10 @@
<AppxOSMaxVersionTestedReplaceManifestVersion>false</AppxOSMaxVersionTestedReplaceManifestVersion>
<OCExecutionAliasName Condition="'$(WindowsTerminalBranding)'==''">wtd</OCExecutionAliasName>
<OCExecutionAliasName Condition="'$(OCExecutionAliasName)'==''">wt</OCExecutionAliasName>
<!-- VS 16.8 causes WAP projects to accidentally package System.Core.dll (from the CLR).
This has been fixed in VS 16.9 using the property below. It's safe for us to include
it here, even after 16.9 comes out. -->
<AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>
</PropertyGroup>
<PropertyGroup>
<ProjectGuid>CA5CAD1A-224A-4171-B13A-F16E576FDD12</ProjectGuid>

View File

@@ -82,14 +82,9 @@
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
<!-- Due to a bug in the OS, this doesn't actually work right -
we'll get a nullptr in our implementation. So this is disabled
temporarily. See MSFT:24623699 for more details.
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
-->
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>

View File

@@ -82,15 +82,9 @@
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
<!-- Due to a bug in the OS, this doesn't actually work right -
we'll get a nullptr in our implementation. So this is disabled
temporarily. See MSFT:24623699 for more details.
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
-->
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>

View File

@@ -83,14 +83,9 @@
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
<!-- Due to a bug in the OS, this doesn't actually work right -
we'll get a nullptr in our implementation. So this is disabled
temporarily. See MSFT:24623699 for more details.
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
-->
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>

View File

@@ -1,119 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{2658e9b0-bd84-48b7-bb5e-3d18f4df07ba}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>Microsoft.Terminal.CommandlineArgs</RootNamespace>
<ProjectName>CommandlineArgs</ProjectName>
<TargetName>CommandlineArgs</TargetName>
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
<ConfigurationType>StaticLibrary</ConfigurationType>
<SubSystem>Console</SubSystem>
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
<ClCompile>
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
on being compiled with RTTI (/GR). -->
<RuntimeTypeInfo>true</RuntimeTypeInfo>
</ClCompile>
</ItemDefinitionGroup>
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="AppCommandlineArgs.h" />
<ClInclude Include="Commandline.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="AppCommandlineArgs.cpp" />
<ClCompile Include="Commandline.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<!--
the packaging project won't recurse through our dependencies, you have to
make sure that if you add a cppwinrt dependency to any of these projects,
you also update all the consumers
-->
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\WinRTUtils\WinRTUtils.vcxproj">
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<!-- For whatever reason, we can't include the TerminalControl and
TerminalSettings projects' winmds via project references. So we'll have to
manually include the winmds as References below -->
</ItemGroup>
<PropertyGroup>
<!-- This is a hack to get the ARM64 CI build working. See
https://github.com/Microsoft/msbuild/issues/3746 - it looks like MsBuild
just has a bug in it.-->
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>Warning</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<ItemGroup>
<!-- Manually add references to each of our dependent winmds. Mark them as
private=false and CopyLocalSatelliteAssemblies=false, so that we don't
propagate them upwards (which can make referencing this project result in
duplicate type definitions)-->
<!-- Despite this lib only _really_ depending on the settings model, we need
to reference Connection, Control, and MUX here, because they have types that
TSM depends on, and the midl compiler will be mad if it can't find them. -->
<Reference Include="Microsoft.Terminal.TerminalConnection">
<HintPath>$(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="Microsoft.Terminal.TerminalControl">
<HintPath>$(OpenConsoleCommonOutDir)TerminalControl\Microsoft.Terminal.TerminalControl.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="Microsoft.Terminal.Settings.Model">
<HintPath>$(OpenConsoleCommonOutDir)Microsoft.Terminal.Settings.Model\Microsoft.Terminal.Settings.Model.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalDependencies>WindowsApp.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
<!-- <Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" /> -->
</Project>

View File

@@ -1,4 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"

View File

@@ -1,53 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// pch.h
// Header for platform projection include files
//
#pragma once
#define WIN32_LEAN_AND_MEAN
#define NOMCX
#define NOHELP
#define NOCOMM
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
#include <LibraryIncludes.h>
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
// SDK definition of this function, so the only fix is to undef it.
// from WinBase.h
// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
#include <wil/cppwinrt.h>
#include <unknwn.h>
#include <hstring.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
// You might think "we're not doing any UI in this lib", and you'd be right.
// However, we need this for Windows.UI.Color
#include <winrt/windows.ui.h>
// Including TraceLogging essentials for the binary
#include <TraceLoggingProvider.h>
#include <winmeta.h>
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <telemetry/ProjectTelemetry.h>
#include <TraceLoggingActivity.h>
#include <shellapi.h>
#include <shobjidl_core.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>
#include <CLI11/CLI11.hpp>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"

View File

@@ -35,6 +35,7 @@ namespace SettingsModelLocalTests
TEST_METHOD(CanLayerColorScheme);
TEST_METHOD(LayerColorSchemeProperties);
TEST_METHOD(LayerColorSchemesOnArray);
TEST_METHOD(UpdateSchemeReferences);
TEST_CLASS_SETUP(ClassSetup)
{
@@ -290,4 +291,81 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_Background);
}
}
void ColorSchemeTests::UpdateSchemeReferences()
{
const std::string settingsString{ R"json({
"defaultProfile": "Inherited reference",
"profiles": {
"defaults": {
"colorScheme": "Scheme 1"
},
"list": [
{
"name": "Explicit scheme reference",
"colorScheme": "Scheme 1"
},
{
"name": "Explicit reference; hidden",
"colorScheme": "Scheme 1",
"hidden": true
},
{
"name": "Inherited reference"
},
{
"name": "Different reference",
"colorScheme": "Scheme 2"
}
]
},
"schemes": [
{ "name": "Scheme 1" },
{ "name": "Scheme 2" },
{ "name": "Scheme 1 (renamed)" }
]
})json" };
auto settings{ winrt::make_self<CascadiaSettings>(false) };
settings->_ParseJsonString(settingsString, false);
settings->_ApplyDefaultsFromUserSettings();
settings->LayerJson(settings->_userSettings);
settings->_ValidateSettings();
// update all references to "Scheme 1"
const auto newName{ L"Scheme 1 (renamed)" };
settings->UpdateColorSchemeReferences(L"Scheme 1", newName);
// verify profile defaults
Log::Comment(L"Profile Defaults");
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().ColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().HasColorSchemeName());
// verify all other profiles
const auto& profiles{ settings->AllProfiles() };
{
const auto& prof{ profiles.GetAt(0) };
Log::Comment(prof.Name().c_str());
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
VERIFY_IS_TRUE(prof.HasColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(1) };
Log::Comment(prof.Name().c_str());
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
VERIFY_IS_TRUE(prof.HasColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(2) };
Log::Comment(prof.Name().c_str());
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
VERIFY_IS_FALSE(prof.HasColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(3) };
Log::Comment(prof.Name().c_str());
VERIFY_ARE_EQUAL(L"Scheme 2", prof.ColorSchemeName());
VERIFY_IS_TRUE(prof.HasColorSchemeName());
}
}
}

View File

@@ -37,6 +37,7 @@ namespace SettingsModelLocalTests
TEST_METHOD(ManyCommandsSameAction);
TEST_METHOD(LayerCommand);
TEST_METHOD(TestSplitPaneArgs);
TEST_METHOD(TestSplitPaneBadSize);
TEST_METHOD(TestResourceKeyName);
TEST_METHOD(TestAutogeneratedName);
TEST_METHOD(TestLayerOnAutogeneratedName);
@@ -147,7 +148,8 @@ namespace SettingsModelLocalTests
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
{ "name": "command4", "command": { "action": "splitPane" } },
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } }
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
{ "name": "command6", "command": { "action": "splitPane", "size": 0.25 } },
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
@@ -156,7 +158,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(4u, commands.Size());
VERIFY_ARE_EQUAL(5u, commands.Size());
{
auto command = commands.Lookup(L"command1");
@@ -167,6 +169,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command2");
@@ -177,6 +180,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command4");
@@ -187,6 +191,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command5");
@@ -197,8 +202,51 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command6");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
}
}
void CommandTests::TestSplitPaneBadSize()
{
const std::string commands0String{ R"([
{ "name": "command1", "command": { "action": "splitPane", "size": 0.25 } },
{ "name": "command2", "command": { "action": "splitPane", "size": 1.0 } },
{ "name": "command3", "command": { "action": "splitPane", "size": 0 } },
{ "name": "command4", "command": { "action": "splitPane", "size": 50 } },
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(3u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
{
auto command = commands.Lookup(L"command1");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
}
}
void CommandTests::TestResourceKeyName()
{
// This test checks looking up a name from a resource key.

View File

@@ -79,6 +79,7 @@ namespace SettingsModelLocalTests
TEST_METHOD(TestCommandsAndKeybindings);
TEST_METHOD(TestNestedCommandWithoutName);
TEST_METHOD(TestNestedCommandWithBadSubCommands);
TEST_METHOD(TestUnbindNestedCommand);
TEST_METHOD(TestRebindNestedCommand);
@@ -1376,7 +1377,7 @@ namespace SettingsModelLocalTests
},
{
"name": "parent",
"commands": [
"commands": [
{ "command": { "action": "setColorScheme", "colorScheme": "invalidScheme" } }
]
}
@@ -1403,11 +1404,11 @@ namespace SettingsModelLocalTests
},
{
"name": "grandparent",
"commands": [
"commands": [
{
"name": "parent",
"commands": [
{
{
"command": { "action": "setColorScheme", "colorScheme": "invalidScheme" }
}
]
@@ -1909,7 +1910,8 @@ namespace SettingsModelLocalTests
"keybindings": [
{ "command": { "action": "splitPane", "split":"auto" }, "keys": [ "ctrl+alt+t", "ctrl+a" ] },
{ "command": { "action": "moveFocus" }, "keys": [ "ctrl+a" ] },
{ "command": { "action": "resizePane" }, "keys": [ "ctrl+b" ] }
{ "command": { "action": "resizePane" }, "keys": [ "ctrl+b" ] },
{ "name": "invalid nested", "commands":[ { "name" : "hello" }, { "name" : "world" } ] }
]
})" };
@@ -1918,18 +1920,20 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->_globals->_keymap->_keyShortcuts.size());
VERIFY_ARE_EQUAL(3u, settings->_globals->_keybindingsWarnings.size());
VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size());
VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(1));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(2));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_globals->_keybindingsWarnings.at(3));
settings->_ValidateKeybindings();
VERIFY_ARE_EQUAL(4u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(5u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_warnings.GetAt(1));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(2));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(3));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(4));
}
void DeserializationTests::ValidateExecuteCommandlineWarning()
@@ -2316,6 +2320,53 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, commands.Size());
}
void DeserializationTests::TestNestedCommandWithBadSubCommands()
{
// This test tests a nested command without a name specified. This type
// of command should just be ignored, since we can't auto-generate names
// for nested commands, they _must_ have names specified.
const std::string settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe"
}
],
"actions": [
{
"name": "nested command",
"commands": [
{
"name": "child1"
},
{
"name": "child2"
}
]
},
],
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
VerifyParseSucceeded(settingsJson);
auto settings = winrt::make_self<implementation::CascadiaSettings>();
settings->_ParseJsonString(settingsJson, false);
settings->LayerJson(settings->_userSettings);
auto commands = settings->_globals->Commands();
settings->_ValidateSettings();
VERIFY_ARE_EQUAL(2u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0));
VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(1));
VERIFY_ARE_EQUAL(0u, commands.Size());
}
void DeserializationTests::TestUnbindNestedCommand()
{
// Test that layering a command with `"commands": null` set will unbind a command that already exists.

View File

@@ -226,6 +226,7 @@ namespace SettingsModelLocalTests
const std::string settingsString{ R"({
"$schema": "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
"profiles": {
"defaults": {

View File

@@ -47,6 +47,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(ParseSplitPaneIntoArgs);
TEST_METHOD(ParseComboCommandlineIntoArgs);
TEST_METHOD(ParseFocusTabArgs);
TEST_METHOD(ParseMoveFocusArgs);
TEST_METHOD(ParseArgumentsWithParsingTerminators);
TEST_METHOD(ParseNoCommandIsNewTab);
@@ -61,6 +62,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestLaunchMode);
TEST_METHOD(TestLaunchModeWithNoCommand);
TEST_METHOD(TestMultipleSplitPaneSizes);
private:
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
const size_t expectedSubcommands,
@@ -76,6 +79,23 @@ namespace TerminalAppLocalTests
appArgs.ValidateStartupCommands();
}
void _buildCommandlinesExpectFailureHelper(AppCommandlineArgs& appArgs,
const size_t expectedSubcommands,
std::vector<const wchar_t*>& rawCommands)
{
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(expectedSubcommands, commandlines.size());
for (auto& cmdBlob : commandlines)
{
const auto result = appArgs.ParseCommand(cmdBlob);
VERIFY_ARE_NOT_EQUAL(0, result);
VERIFY_ARE_NOT_EQUAL("", appArgs._exitMessage);
Log::Comment(NoThrowString().Format(
L"Exit Message:\n%hs",
appArgs._exitMessage.c_str()));
}
}
void _logCommandline(std::vector<const wchar_t*>& rawCommands)
{
std::wstring buffer;
@@ -995,6 +1015,124 @@ namespace TerminalAppLocalTests
}
}
void CommandlineTest::ParseMoveFocusArgs()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
END_TEST_METHOD_PROPERTIES()
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `mf` instead of `move-focus`");
const wchar_t* subcommand = useShortForm ? L"mf" : L"move-focus";
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
Log::Comment(NoThrowString().Format(
L"Just the subcommand, without a direction, should fail."));
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.FocusDirection());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.FocusDirection());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"up" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.FocusDirection());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"down" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.FocusDirection());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"badDirection" };
Log::Comment(NoThrowString().Format(
L"move-focus with an invalid direction should fail."));
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left", L";", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.FocusDirection());
actionAndArgs = appArgs._startupActions.at(2);
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.FocusDirection());
}
}
void CommandlineTest::ValidateFirstCommandIsNewTab()
{
AppCommandlineArgs appArgs{};
@@ -1325,4 +1463,119 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline());
}
}
void CommandlineTest::TestMultipleSplitPaneSizes()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
END_TEST_METHOD_PROPERTIES()
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `sp` instead of `split-pane`");
const wchar_t* subcommand = useShortForm ? L"sp" : L"split-pane";
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
// The one we actually want to test here is the SplitPane action we created
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-s", L".3" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
// The one we actually want to test here is the SplitPane action we created
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-s", L".3", L";", subcommand };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
{
// The one we actually want to test here is the SplitPane action we created
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
auto actionAndArgs = appArgs._startupActions.at(2);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-s", L".3", L";", subcommand, L"-s", L".7" };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
{
// The one we actually want to test here is the SplitPane action we created
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
auto actionAndArgs = appArgs._startupActions.at(2);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.7f, myArgs.SplitSize());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
}
}
}

View File

@@ -486,7 +486,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
@@ -504,7 +504,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));

View File

@@ -14,16 +14,6 @@
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
<ClCompile>
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
on being compiled with RTTI (/GR). We need this in this project, because
we're including the CLI11 header via the CommandlineArgs project. -->
<RuntimeTypeInfo>true</RuntimeTypeInfo>
</ClCompile>
</ItemDefinitionGroup>
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="Monarch.h">
@@ -84,46 +74,9 @@
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\CommandlineArgs\CommandlineArgs.vcxproj" >
<Private>true</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<!-- Make sure to set ReferenceOutputAssembly=false, or we'll try and
MDMERGE with MUX from the CommandlineArgs output, and get duplicate type
errors everywhere. -->
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<!-- This is going to turn into a disaster. We still need to reference MUX
to get mdmerge happy, but we can't roll that up without rolling up the DLL
and now we'll get duplicate types in WT.vcxproj. GAH. We'll need to have
Remoting accept a callback to say "I encountered a commandline, someone,
deal with it?", then have apphost register for that callback. So the flow
is monarch->AppHost->AppLogic -->
</ProjectReference>
<!-- For whatever reason, we can't include the TerminalControl and
TerminalSettings projects' winmds via project references. So we'll have to
manually include the winmds as References below -->
<Reference Include="Microsoft.Terminal.TerminalConnection">
<HintPath>$(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="Microsoft.Terminal.TerminalControl">
<HintPath>$(OpenConsoleCommonOutDir)TerminalControl\Microsoft.Terminal.TerminalControl.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="Microsoft.Terminal.Settings.Model">
<HintPath>$(OpenConsoleCommonOutDir)Microsoft.Terminal.Settings.Model\Microsoft.Terminal.Settings.Model.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>

View File

@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Monarch.h"
#include "CommandlineArgs.h"
#include "../CommandlineArgs/AppCommandlineArgs.h"
#include "Monarch.g.cpp"
#include "../../types/inc/utils.hpp"
@@ -44,7 +46,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
// TODO:projects/5 This is terrible. There's gotta be a better way
// of finding the first opening in a non-consecutive map of int->object
auto providedID = peasant.GetID();
const auto providedID = peasant.GetID();
if (providedID == 0)
{
@@ -64,6 +66,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// Add an event listener to the peasant's WindowActivated event.
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
// TODO:projects/5 Wait on the peasant's PID, and remove them from the
// map if they die. This won't work great in tests though, with fake
// PIDs.
return newPeasantsId;
}
@@ -96,23 +102,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// - the peasant if it exists in our map, otherwise null
Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID)
{
try
{
auto peasantSearch = _peasants.find(peasantID);
auto maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
if (maybeThePeasant)
{
maybeThePeasant.GetPID();
}
return maybeThePeasant;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
// Remove the peasant from the list of peasants
_peasants.erase(peasantID);
return nullptr;
}
auto peasantSearch = _peasants.find(peasantID);
return peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
}
void Monarch::_setMostRecentPeasant(const uint64_t peasantID)
@@ -140,9 +131,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// always want to create a new window, so we'll just return true. This
// will tell the caller that we didn't handle the commandline, and they
// should open a new window to deal with it themselves.
CommandlineArgs::AppCommandlineArgs argParser;
return true;
}

View File

@@ -1,15 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "Monarch.g.h"
#include "Peasant.h"
#include "../cascadia/inc/cppwinrt_utils.h"
// {06171993-7eb1-4f3e-85f5-8bdd7386cce3}
constexpr GUID Monarch_clsid{
// We sure different GUIDs here depending on whether we're running a Release,
// Preview, or Dev build. This ensures that different installs don't
// accidentally talk to one another.
//
// * Release: {06171993-7eb1-4f3e-85f5-8bdd7386cce3}
// * Preview: {04221993-7eb1-4f3e-85f5-8bdd7386cce3}
// * Dev: {08302020-7eb1-4f3e-85f5-8bdd7386cce3}
constexpr GUID Monarch_clsid
{
#if defined(WT_BRANDING_RELEASE)
0x06171993,
0x7eb1,
0x4f3e,
{ 0x85, 0xf5, 0x8b, 0xdd, 0x73, 0x86, 0xcc, 0xe3 }
#elif defined(WT_BRANDING_PREVIEW)
0x04221993,
#else
0x08302020,
#endif
0x7eb1,
0x4f3e,
{
0x85, 0xf5, 0x8b, 0xdd, 0x73, 0x86, 0xcc, 0xe3
}
};
enum class WindowingBehavior : uint64_t

View File

@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "Peasant.idl";
namespace Microsoft.Terminal.Remoting

View File

@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Monarch.h"
@@ -8,11 +11,6 @@
// per-process. Once the first monarch is created, we'll stash it in g_weak.
// Future callers who try to instantiate a Monarch will get the one that's
// already been made.
//
// I'm sure there's a better way to do this with WRL, but I'm not familiar
// enough with WRL to know for sure.
winrt::weak_ref<winrt::Microsoft::Terminal::Remoting::implementation::Monarch> g_weak{ nullptr };
struct MonarchFactory : winrt::implements<MonarchFactory, ::IClassFactory>
{
@@ -20,24 +18,26 @@ struct MonarchFactory : winrt::implements<MonarchFactory, ::IClassFactory>
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept
{
static winrt::weak_ref<winrt::Microsoft::Terminal::Remoting::implementation::Monarch> g_weak{ nullptr };
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!g_weak)
// Lock the ref immediately. We don't want it freed from out beneath us
auto strong = g_weak.get();
if (!strong)
{
// Create a new Monarch instance
auto strong = winrt::make_self<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
strong = winrt::make_self<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
g_weak = (*strong).get_weak();
return strong.as(iid, result);
}
else
{
// We already instantiated one Monarch, let's just return that one!
auto strong = g_weak.get();
return strong.as(iid, result);
}
}

View File

@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Peasant.h"
#include "CommandlineArgs.h"

View File

@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "Peasant.g.h"

View File

@@ -1,4 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Remoting
{

View File

@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "WindowManager.h"
#include "MonarchFactory.h"
@@ -15,12 +18,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
WindowManager::WindowManager()
{
_monarchWaitInterrupt.create();
// Register with COM as a server for the Monarch class
_registerAsMonarch();
// Instantiate an instance of the Monarch. This may or may not be in-proc!
_createMonarchAndCallbacks();
_createMonarch();
}
WindowManager::~WindowManager()
@@ -31,12 +32,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// monarch!
CoRevokeClassObject(_registrationHostClass);
_registrationHostClass = 0;
_monarchWaitInterrupt.SetEvent();
if (_electionThread.joinable())
{
_electionThread.join();
}
}
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
@@ -55,12 +50,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// instance, and tell that peasant to handle that commandline.
_createOurPeasant();
// TODO:MG Spawn a thread to wait on the monarch, and handle the election
if (!isKing)
{
_createPeasantThread();
}
_peasant.ExecuteCommandline(args);
}
// Otherwise, we'll do _nothing_.
@@ -83,7 +72,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void WindowManager::_createMonarch()
{
// Heads up! This only works because we're using
// "metadata-based-marshalling" for our WinRT types. THat means the OS is
// "metadata-based-marshalling" for our WinRT types. That means the OS is
// using the .winmd file we generate to figure out the proxy/stub
// definitions for our types automatically. This only works in the following
// cases:
@@ -94,25 +83,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
CLSCTX_LOCAL_SERVER);
}
void WindowManager::_createMonarchAndCallbacks()
{
_createMonarch();
const auto isKing = _areWeTheKing();
if (!isKing)
{
return;
}
// Here, we're the king!
// Wait, don't. Let's just have the monarch try/catch any accesses to
// peasants. If the peasant dies, then it can't get the peasant's
// anything. In that case, _remove it_.
}
bool WindowManager::_areWeTheKing()
{
auto kingPID = _monarch.GetPID();
auto ourPID = GetCurrentProcessId();
const auto kingPID{ _monarch.GetPID() };
const auto ourPID{ GetCurrentProcessId() };
return (ourPID == kingPID);
}
@@ -122,89 +96,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_peasant = *p;
_monarch.AddPeasant(_peasant);
// TODO:projects/5 Spawn a thread to wait on the monarch, and handle the election
return _peasant;
}
bool WindowManager::_electionNight2020()
{
_createMonarchAndCallbacks();
// Tell the new monarch who we are. We might be that monarch!
_monarch.AddPeasant(_peasant);
if (_areWeTheKing())
{
return true;
}
return false;
}
void WindowManager::_createPeasantThread()
{
// If we catch an exception trying to get at the monarch ever, we can
// set the _monarchWaitInterrupt, and use that to trigger a new
// election. Though, we wouldn't be able to retry the function that
// caused the exception in the first place...
_electionThread = std::thread([this] {
_waitOnMonarchThread();
});
}
void WindowManager::_waitOnMonarchThread()
{
HANDLE waits[2];
waits[1] = _monarchWaitInterrupt.get();
bool exitRequested = false;
while (!exitRequested)
{
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(_monarch.GetPID())) };
// TODO:MG If we fail to open the monarch, then they don't exist
// anymore! Go straight to an election.
//
// TODO:MG At any point in all this, the current monarch might die.
// We go straight to a new election, right? Worst case, eventually,
// we'll become the new monarch.
//
// if (hMonarch.get() == nullptr)
// {
// const auto gle = GetLastError();
// return false;
// }
waits[0] = hMonarch.get();
auto waitResult = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
switch (waitResult)
{
case WAIT_OBJECT_0 + 0: // waits[0] was signaled
// Connect to the new monarch, which might be us!
// If we become the monarch, then we'll return true and exit this thread.
exitRequested = _electionNight2020();
break;
case WAIT_OBJECT_0 + 1: // waits[1] was signaled
exitRequested = true;
break;
case WAIT_TIMEOUT:
printf("Wait timed out. This should be impossible.\n");
exitRequested = true;
break;
// Return value is invalid.
default:
{
auto gle = GetLastError();
printf("WaitForMultipleObjects returned: %d\n", waitResult);
printf("Wait error: %d\n", gle);
ExitProcess(0);
}
}
}
}
Remoting::Peasant WindowManager::CurrentWindow()
{
return _peasant;

View File

@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "WindowManager.g.h"
@@ -23,19 +26,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::Monarch _monarch{ nullptr };
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
wil::unique_event _monarchWaitInterrupt;
std::thread _electionThread;
void _registerAsMonarch();
void _createMonarch();
void _createMonarchAndCallbacks();
bool _areWeTheKing();
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant();
bool _electionNight2020();
void _createPeasantThread();
void _waitOnMonarchThread();
};
}

View File

@@ -61,7 +61,7 @@
</ClCompile>
<Link>
<AdditionalDependencies>User32.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<!-- TSM Lib contains a DllMain that we need to force the use of. -->
<!-- Our lib contains a DllMain that we need to force the use of. -->
<AdditionalOptions Condition="'$(Platform)'=='Win32'">/INCLUDE:_DllMain@12</AdditionalOptions>
<AdditionalOptions Condition="'$(Platform)'!='Win32'">/INCLUDE:DllMain</AdditionalOptions>
</Link>

View File

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

View File

@@ -44,7 +44,5 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
#include <shellapi.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include "OpenTerminalHere.h"
#include <ShlObj.h>
// TODO GH#6112: Localize these strings
static constexpr std::wstring_view VerbDisplayName{ L"Open in Windows Terminal" };
@@ -117,14 +118,29 @@ static std::wstring _getExePath()
HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray,
IBindCtx* /*pBindContext*/)
{
DWORD count;
psiItemArray->GetCount(&count);
winrt::com_ptr<IShellItem> psi;
RETURN_IF_FAILED(psiItemArray->GetItemAt(0, psi.put()));
wil::unique_cotaskmem_string pszName;
RETURN_IF_FAILED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName));
if (psiItemArray == nullptr)
{
// get the current path from explorer.exe
const auto path = this->_GetPathFromExplorer();
// no go, unable to get a reasonable path
if (path.empty())
{
return S_FALSE;
}
pszName = wil::make_cotaskmem_string(path.c_str(), path.length());
}
else
{
DWORD count;
psiItemArray->GetCount(&count);
winrt::com_ptr<IShellItem> psi;
RETURN_IF_FAILED(psiItemArray->GetItemAt(0, psi.put()));
RETURN_IF_FAILED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName));
}
{
wil::unique_process_information _piClient;
@@ -214,3 +230,103 @@ HRESULT OpenTerminalHere::EnumSubCommands(IEnumExplorerCommand** ppEnum)
*ppEnum = nullptr;
return E_NOTIMPL;
}
std::wstring OpenTerminalHere::_GetPathFromExplorer() const
{
using namespace std;
using namespace winrt;
wstring path;
HRESULT hr = NOERROR;
auto hwnd = ::GetForegroundWindow();
if (hwnd == nullptr)
{
return path;
}
TCHAR szName[MAX_PATH] = { 0 };
::GetClassName(hwnd, szName, MAX_PATH);
if (0 == StrCmp(szName, L"WorkerW") ||
0 == StrCmp(szName, L"Progman"))
{
//special folder: desktop
hr = ::SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, szName);
if (FAILED(hr))
{
return path;
}
path = szName;
return path;
}
if (0 != StrCmp(szName, L"CabinetWClass"))
{
return path;
}
com_ptr<IShellWindows> shell;
try
{
shell = create_instance<IShellWindows>(CLSID_ShellWindows, CLSCTX_ALL);
}
catch (...)
{
//look like try_create_instance is not available no more
}
if (shell == nullptr)
{
return path;
}
com_ptr<IDispatch> disp;
wil::unique_variant variant;
variant.vt = VT_I4;
com_ptr<IWebBrowserApp> browser;
// look for correct explorer window
for (variant.intVal = 0;
shell->Item(variant, disp.put()) == S_OK;
variant.intVal++)
{
com_ptr<IWebBrowserApp> tmp;
if (FAILED(disp->QueryInterface(tmp.put())))
{
disp = nullptr; // get rid of DEBUG non-nullptr warning
continue;
}
HWND tmpHWND = NULL;
hr = tmp->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&tmpHWND));
if (hwnd == tmpHWND)
{
browser = tmp;
disp = nullptr; // get rid of DEBUG non-nullptr warning
break; //found
}
disp = nullptr; // get rid of DEBUG non-nullptr warning
}
if (browser != nullptr)
{
wil::unique_bstr url;
hr = browser->get_LocationURL(&url);
if (FAILED(hr))
{
return path;
}
wstring sUrl(url.get(), SysStringLen(url.get()));
DWORD size = MAX_PATH;
hr = ::PathCreateFromUrl(sUrl.c_str(), szName, &size, NULL);
if (SUCCEEDED(hr))
{
path = szName;
}
}
return path;
}

View File

@@ -46,6 +46,9 @@ struct __declspec(uuid("9f156763-7844-4dc4-b2b1-901f640f5155"))
STDMETHODIMP GetCanonicalName(GUID* pguidCommandName);
STDMETHODIMP EnumSubCommands(IEnumExplorerCommand** ppEnum);
#pragma endregion
private:
std::wstring _GetPathFromExplorer() const;
};
CoCreatableClass(OpenTerminalHere);

View File

@@ -11,10 +11,13 @@
<!-- sets a bunch of Windows Universal properties -->
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>User32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="OpenTerminalHere.h" />
@@ -40,7 +43,6 @@
<None Include="packages.config" />
<None Include="WindowsTerminalShellExt.def" />
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<!--
@@ -56,7 +58,5 @@
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
</Project>

View File

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

View File

@@ -17,6 +17,7 @@ using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
using namespace ::TerminalApp;
namespace winrt
{
@@ -121,7 +122,11 @@ namespace winrt::TerminalApp::implementation
}
else if (const auto& realArgs = args.ActionArgs().try_as<SplitPaneArgs>())
{
_SplitPane(realArgs.SplitStyle(), realArgs.SplitMode(), realArgs.TerminalArgs());
_SplitPane(realArgs.SplitStyle(),
realArgs.SplitMode(),
// This is safe, we're already filtering so the value is (0, 1)
::base::saturated_cast<float>(realArgs.SplitSize()),
realArgs.TerminalArgs());
args.Handled(true);
}
}
@@ -129,23 +134,20 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (auto focusedTab = _GetFocusedTab())
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
// Don't do anything if there's only one pane. It's already zoomed.
if (activeTab->GetLeafPaneCount() > 1)
{
// Don't do anything if there's only one pane. It's already zoomed.
if (activeTab && activeTab->GetLeafPaneCount() > 1)
{
// First thing's first, remove the current content from the UI
// tree. This is important, because we might be leaving zoom, and if
// a pane is zoomed, then it's currently in the UI tree, and should
// be removed before it's re-added in Pane::Restore
_tabContent.Children().Clear();
// First thing's first, remove the current content from the UI
// tree. This is important, because we might be leaving zoom, and if
// a pane is zoomed, then it's currently in the UI tree, and should
// be removed before it's re-added in Pane::Restore
_tabContent.Children().Clear();
// Togging the zoom on the tab will cause the tab to inform us of
// the new root Content for this tab.
activeTab->ToggleZoom();
}
// Togging the zoom on the tab will cause the tab to inform us of
// the new root Content for this tab.
activeTab->ToggleZoom();
}
}
@@ -342,19 +344,16 @@ namespace winrt::TerminalApp::implementation
args.Handled(false);
if (const auto& realArgs = args.ActionArgs().try_as<SetColorSchemeArgs>())
{
if (auto focusedTab = _GetFocusedTab())
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
if (auto activeControl = activeTab->GetActiveTerminalControl())
{
if (auto activeControl = activeTab->GetActiveTerminalControl())
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
{
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
{
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
controlSettings->ApplyColorScheme(scheme);
activeControl.UpdateSettings(*controlSettings);
args.Handled(true);
}
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
controlSettings->ApplyColorScheme(scheme);
activeControl.UpdateSettings(*controlSettings);
args.Handled(true);
}
}
}
@@ -371,18 +370,15 @@ namespace winrt::TerminalApp::implementation
tabColor = realArgs.TabColor();
}
if (auto focusedTab = _GetFocusedTab())
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
if (tabColor)
{
if (tabColor)
{
activeTab->SetRuntimeTabColor(tabColor.Value());
}
else
{
activeTab->ResetRuntimeTabColor();
}
activeTab->SetRuntimeTabColor(tabColor.Value());
}
else
{
activeTab->ResetRuntimeTabColor();
}
}
args.Handled(true);
@@ -391,12 +387,9 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (auto focusedTab = _GetFocusedTab())
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
activeTab->ActivateColorPicker();
}
activeTab->ActivateColorPicker();
}
args.Handled(true);
}
@@ -411,18 +404,15 @@ namespace winrt::TerminalApp::implementation
title = realArgs.Title();
}
if (auto focusedTab = _GetFocusedTab())
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
if (title.has_value())
{
if (title.has_value())
{
activeTab->SetTabText(title.value());
}
else
{
activeTab->ResetTabText();
}
activeTab->SetTabText(title.value());
}
else
{
activeTab->ResetTabText();
}
}
args.Handled(true);
@@ -431,12 +421,9 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleOpenTabRenamer(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (auto focusedTab = _GetFocusedTab())
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
activeTab->ActivateTabRenamer();
}
activeTab->ActivateTabRenamer();
}
args.Handled(true);
}
@@ -534,13 +521,8 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleOpenTabSearch(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
// Tab search is always in-order.
CommandPalette().SetTabs(_tabs, true);
auto opt = _GetFocusedTabIndex();
uint32_t startIdx = opt.value_or(0);
CommandPalette().EnableTabSwitcherMode(true, startIdx);
CommandPalette().SetTabs(_tabs, _mruTabs);
CommandPalette().EnableTabSearchMode();
CommandPalette().Visibility(Visibility::Visible);
args.Handled(true);

View File

@@ -2,12 +2,14 @@
// Licensed under the MIT license.
#include "pch.h"
#include "AppLogic.h"
#include "AppCommandlineArgs.h"
#include "../types/inc/utils.hpp"
#include <LibraryResources.h>
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace CommandlineArgs;
using namespace TerminalApp;
// Either a ; at the start of a line, or a ; preceded by any non-\ char.
const std::wregex AppCommandlineArgs::_commandDelimiterRegex{ LR"(^;|[^\\];)" };
@@ -187,6 +189,7 @@ void AppCommandlineArgs::_buildParser()
_buildNewTabParser();
_buildSplitPaneParser();
_buildFocusTabParser();
_buildMoveFocusParser();
}
// Method Description:
@@ -245,6 +248,10 @@ void AppCommandlineArgs::_buildSplitPaneParser()
_splitVertical,
RS_A(L"CmdSplitPaneVerticalArgDesc"));
subcommand._verticalOption->excludes(subcommand._horizontalOption);
auto* sizeOpt = subcommand.subcommand->add_option("-s,--size",
_splitPaneSize,
RS_A(L"CmdSplitPaneSizeArgDesc"));
sizeOpt->check(CLI::Range(0.01f, 0.99f));
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
@@ -272,7 +279,7 @@ void AppCommandlineArgs::_buildSplitPaneParser()
style = SplitState::Vertical;
}
}
SplitPaneArgs args{ style, terminalArgs };
SplitPaneArgs args{ style, _splitPaneSize, terminalArgs };
splitPaneActionAndArgs.Args(args);
_startupActions.push_back(splitPaneActionAndArgs);
});
@@ -335,6 +342,54 @@ void AppCommandlineArgs::_buildFocusTabParser()
setupSubcommand(_focusTabShort);
}
// Method Description:
// - Adds the `move-focus` subcommand and related options to the commandline parser.
// - Additionally adds the `mf` subcommand, which is just a shortened version of `move-focus`
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildMoveFocusParser()
{
_moveFocusCommand = _app.add_subcommand("move-focus", RS_A(L"CmdMoveFocusDesc"));
_moveFocusShort = _app.add_subcommand("mf", RS_A(L"CmdMFDesc"));
auto setupSubcommand = [this](auto* subcommand) {
std::map<std::string, FocusDirection> map = {
{ "left", FocusDirection::Left },
{ "right", FocusDirection::Right },
{ "up", FocusDirection::Up },
{ "down", FocusDirection::Down }
};
auto* directionOpt = subcommand->add_option("direction",
_moveFocusDirection,
RS_A(L"CmdMoveFocusDirectionArgDesc"));
directionOpt->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
directionOpt->required();
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
if (_moveFocusDirection != FocusDirection::None)
{
MoveFocusArgs args{ _moveFocusDirection };
ActionAndArgs actionAndArgs{};
actionAndArgs.Action(ShortcutAction::MoveFocus);
actionAndArgs.Args(args);
_startupActions.push_back(std::move(actionAndArgs));
}
});
};
setupSubcommand(_moveFocusCommand);
setupSubcommand(_moveFocusShort);
}
// Method Description:
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
// that subcommand to support all the properties in a NewTerminalArgs.
@@ -442,6 +497,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
*_newTabShort.subcommand ||
*_focusTabCommand ||
*_focusTabShort ||
*_moveFocusCommand ||
*_moveFocusShort ||
*_newPaneShort.subcommand ||
*_newPaneCommand.subcommand);
}
@@ -464,11 +521,13 @@ void AppCommandlineArgs::_resetStateToDefault()
_splitVertical = false;
_splitHorizontal = false;
_splitPaneSize = 0.5f;
_focusTabIndex = -1;
_focusNextTab = false;
_focusPrevTab = false;
_moveFocusDirection = FocusDirection::None;
// DON'T clear _launchMode here! This will get called once for every
// subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
// the "global" fullscreen flag (-F).
@@ -685,7 +744,7 @@ std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> AppComman
// - 0 if the commandline was successfully parsed
int AppCommandlineArgs::ParseArgs(winrt::array_view<const winrt::hstring>& args)
{
auto commands = ::CommandlineArgs::AppCommandlineArgs::BuildCommands(args);
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
for (auto& cmdBlob : commands)
{

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
#pragma once
#include <CLI11/CLI11.hpp>
#include "Commandline.h"
#ifdef UNIT_TESTING
@@ -13,12 +12,12 @@ namespace TerminalAppLocalTests
};
#endif
namespace CommandlineArgs
namespace TerminalApp
{
class AppCommandlineArgs;
};
class CommandlineArgs::AppCommandlineArgs final
class TerminalApp::AppCommandlineArgs final
{
public:
static constexpr std::string_view NixHelpFlag{ "-?" };
@@ -75,6 +74,9 @@ private:
NewPaneSubcommand _newPaneShort;
CLI::App* _focusTabCommand;
CLI::App* _focusTabShort;
CLI::App* _moveFocusCommand;
CLI::App* _moveFocusShort;
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
std::string _profileName;
@@ -82,11 +84,14 @@ private:
std::string _startingTitle;
std::string _startingTabColor;
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
// _commandline will contain the command line with which we'll be spawning a new terminal
std::vector<std::string> _commandline;
bool _splitVertical{ false };
bool _splitHorizontal{ false };
float _splitPaneSize{ 0.5f };
int _focusTabIndex{ -1 };
bool _focusNextTab{ false };
@@ -106,6 +111,7 @@ private:
void _buildNewTabParser();
void _buildSplitPaneParser();
void _buildFocusTabParser();
void _buildMoveFocusParser();
bool _noCommandsProvided();
void _resetStateToDefault();
int _handleExit(const CLI::App& command, const CLI::Error& e);

View File

@@ -17,7 +17,7 @@ using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace ::CommandlineArgs;
using namespace ::TerminalApp;
namespace winrt
{
@@ -42,7 +42,10 @@ static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadWar
USES_RESOURCE(L"LegacyGlobalsProperty"),
USES_RESOURCE(L"FailedToParseCommandJson"),
USES_RESOURCE(L"FailedToWriteToSettings"),
USES_RESOURCE(L"InvalidColorSchemeInCmd")
USES_RESOURCE(L"InvalidColorSchemeInCmd"),
USES_RESOURCE(L"InvalidSplitSize"),
USES_RESOURCE(L"FailedToParseStartupActions"),
USES_RESOURCE(L"FailedToParseSubCommands"),
};
static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels {
USES_RESOURCE(L"NoProfilesText"),
@@ -262,6 +265,14 @@ namespace winrt::TerminalApp::implementation
_settings.GlobalSettings().ShowTabsInTitlebar(false);
}
// Pay attention, that even if some command line arguments were parsed (like launch mode),
// we will not use the startup actions from settings.
// While this simplifies the logic, we might want to reconsider this behavior in the future.
if (!_hasCommandLineArguments && _hasSettingsStartupActions)
{
_root->SetStartupActions(_settingsAppArgs.GetStartupActions());
}
_root->SetSettings(_settings, false);
_root->Loaded({ this, &AppLogic::_OnLoaded });
_root->Initialized([this](auto&&, auto&&) {
@@ -425,8 +436,7 @@ namespace winrt::TerminalApp::implementation
// Make sure the lines of text wrap
warningsTextBlock.TextWrapping(TextWrapping::Wrap);
const auto warnings = _settings.Warnings();
for (const auto& warning : warnings)
for (const auto& warning : _warnings)
{
// Try looking up the warning message key for each warning.
const auto warningText = _GetWarningText(warning);
@@ -715,7 +725,34 @@ namespace winrt::TerminalApp::implementation
return E_INVALIDARG;
}
hr = _settings.Warnings().Size() == 0 ? S_OK : S_FALSE;
_warnings.clear();
for (uint32_t i = 0; i < _settings.Warnings().Size(); i++)
{
_warnings.push_back(_settings.Warnings().GetAt(i));
}
_hasSettingsStartupActions = false;
const auto startupActions = _settings.GlobalSettings().StartupActions();
if (!startupActions.empty())
{
_settingsAppArgs.FullResetState();
ExecuteCommandlineArgs args{ _settings.GlobalSettings().StartupActions() };
auto result = _settingsAppArgs.ParseArgs(args);
if (result == 0)
{
_hasSettingsStartupActions = true;
// Validation also injects new-tab command if implicit new-tab was provided.
_settingsAppArgs.ValidateStartupCommands();
}
else
{
_warnings.push_back(SettingsLoadWarnings::FailedToParseStartupActions);
}
}
hr = _warnings.empty() ? S_OK : S_FALSE;
}
catch (const winrt::hresult_error& e)
{
@@ -805,7 +842,8 @@ namespace winrt::TerminalApp::implementation
// editors, who will write a temp file, then rename it to be the
// actual file you wrote. So listen for that too.
if (!(event == wil::FolderChangeEvent::Modified ||
event == wil::FolderChangeEvent::RenameNewName))
event == wil::FolderChangeEvent::RenameNewName ||
event == wil::FolderChangeEvent::Removed))
{
return;
}
@@ -1104,6 +1142,9 @@ namespace winrt::TerminalApp::implementation
const auto result = _appArgs.ParseArgs(args);
if (result == 0)
{
// If the size of the arguments list is 1,
// then it contains only the executable name and no other arguments.
_hasCommandLineArguments = args.size() > 1;
_appArgs.ValidateStartupCommands();
_root->SetStartupActions(_appArgs.GetStartupActions());
}
@@ -1113,7 +1154,7 @@ namespace winrt::TerminalApp::implementation
int32_t AppLogic::ExecuteCommandline(array_view<const winrt::hstring> args)
{
::CommandlineArgs::AppCommandlineArgs appArgs;
::TerminalApp::AppCommandlineArgs appArgs;
auto result = appArgs.ParseArgs(args);
if (result == 0)
{

View File

@@ -85,7 +85,8 @@ namespace winrt::TerminalApp::implementation
std::atomic<bool> _settingsReloadQueued{ false };
::CommandlineArgs::AppCommandlineArgs _appArgs;
::TerminalApp::AppCommandlineArgs _appArgs;
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
int _ParseArgs(winrt::array_view<const hstring>& args);
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
@@ -107,6 +108,10 @@ namespace winrt::TerminalApp::implementation
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
bool _hasCommandLineArguments{ false };
bool _hasSettingsStartupActions{ false };
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings;
// These are events that are handled by the TerminalPage, but are
// exposed through the AppLogic. This macro is used to forward the event
// directly to them.

View File

@@ -12,7 +12,7 @@
</Style>
</Flyout.FlyoutPresenterStyle>
<StackPanel Orientation="Horizontal">
<StackPanel>
<StackPanel XYFocusKeyboardNavigation="Enabled">
<VariableSizedWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="4" HorizontalAlignment="Center" Margin="0, 3, 0, 0">
<VariableSizedWrapGrid.Resources>
<Style TargetType="Rectangle">

View File

@@ -31,6 +31,7 @@ namespace winrt::TerminalApp::implementation
_currentNestedCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
_allCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
_tabActions = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
_mruTabActions = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
_commandLineHistory = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
_switchToMode(CommandPaletteMode::ActionMode);
@@ -51,6 +52,12 @@ namespace winrt::TerminalApp::implementation
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
if (Visibility() == Visibility::Visible)
{
if (_filteredActionsView().Items().Size() == 0 && _filteredActions.Size() > 0)
{
// Force immediate binding update so we can select an item
Bindings->Update();
}
if (_currentMode == CommandPaletteMode::TabSwitchMode)
{
_searchBox().Visibility(Visibility::Collapsed);
@@ -66,7 +73,6 @@ namespace winrt::TerminalApp::implementation
{
_filteredActionsView().SelectedIndex(0);
_searchBox().Focus(FocusState::Programmatic);
_updateFilteredActions();
}
TraceLoggingWrite(
@@ -112,7 +118,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
void CommandPalette::SelectNextItem(const bool moveDown)
{
const auto selected = _filteredActionsView().SelectedIndex();
auto selected = _filteredActionsView().SelectedIndex();
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
// Do not try to select an item if
@@ -237,6 +243,9 @@ namespace winrt::TerminalApp::implementation
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
// Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because
// they're not considered input key presses. While they don't raise KeyDown events,
@@ -245,55 +254,38 @@ namespace winrt::TerminalApp::implementation
// Only give anchored tab switcher the ability to cycle through tabs with the tab button.
// For unanchored mode, accessibility becomes an issue when we try to hijack tab since it's
// a really widely used keyboard navigation key.
if (_currentMode == CommandPaletteMode::TabSwitchMode && key == VirtualKey::Tab)
if (_currentMode == CommandPaletteMode::TabSwitchMode && _keymap)
{
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift);
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
winrt::Microsoft::Terminal::TerminalControl::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
const auto action = _keymap.TryLookup(kc);
if (action)
{
SelectNextItem(false);
e.Handled(true);
}
else
{
SelectNextItem(true);
e.Handled(true);
if (action.Action() == ShortcutAction::PrevTab)
{
SelectNextItem(false);
e.Handled(true);
return;
}
else if (action.Action() == ShortcutAction::NextTab)
{
SelectNextItem(true);
e.Handled(true);
return;
}
}
}
else if (key == VirtualKey::Home)
{
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control);
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
{
ScrollToTop();
e.Handled(true);
}
}
else if (key == VirtualKey::End)
{
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control);
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
{
ScrollToBottom();
e.Handled(true);
}
}
}
// Method Description:
// - Process keystrokes in the input box. This is used for moving focus up
// and down the list of commands in Action mode, and for executing
// commands in both Action mode and Commandline mode.
// Arguments:
// - e: the KeyRoutedEventArgs containing info about the keystroke.
// Return Value:
// - <none>
void CommandPalette::_keyDownHandler(IInspectable const& /*sender*/,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
if (key == VirtualKey::Up)
if (key == VirtualKey::Home && ctrlDown)
{
ScrollToTop();
e.Handled(true);
}
else if (key == VirtualKey::End && ctrlDown)
{
ScrollToBottom();
e.Handled(true);
}
else if (key == VirtualKey::Up)
{
// Action Mode: Move focus to the next item in the list.
SelectNextItem(false);
@@ -339,41 +331,23 @@ namespace winrt::TerminalApp::implementation
e.Handled(true);
}
else if (key == VirtualKey::Back)
else if (key == VirtualKey::Back && _searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
{
// If the last filter text was empty, and we're backspacing from
// that state, then the user "backspaced" the virtual '>' we're
// using as the action mode indicator. Switch into commandline mode.
if (_searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
{
_switchToMode(CommandPaletteMode::CommandlineMode);
}
_switchToMode(CommandPaletteMode::CommandlineMode);
e.Handled(true);
}
else
else if (key == VirtualKey::C && ctrlDown)
{
const auto vkey = ::gsl::narrow_cast<WORD>(e.OriginalKey());
// In the interest of not telling all modes to check for keybindings, limit to TabSwitch mode for now.
if (_currentMode == CommandPaletteMode::TabSwitchMode)
{
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
auto success = _bindings.TryKeyChord({
ctrlDown,
altDown,
shiftDown,
vkey,
});
if (success)
{
e.Handled(true);
}
}
_searchBox().CopySelectionToClipboard();
e.Handled(true);
}
else if (key == VirtualKey::V && ctrlDown)
{
_searchBox().PasteFromClipboard();
e.Handled(true);
}
}
@@ -462,6 +436,12 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_lostFocusHandler(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
{
const auto flyout = _searchBox().ContextFlyout();
if (flyout && flyout.IsOpen())
{
return;
}
auto focusedElementOrAncestor = Input::FocusManager::GetFocusedElement(this->XamlRoot()).try_as<DependencyObject>();
while (focusedElementOrAncestor)
{
@@ -578,7 +558,7 @@ namespace winrt::TerminalApp::implementation
case CommandPaletteMode::TabSearchMode:
return _tabActions;
case CommandPaletteMode::TabSwitchMode:
return _tabActions;
return _tabSwitcherMode == TabSwitcherMode::MostRecentlyUsed ? _mruTabActions : _tabActions;
case CommandPaletteMode::CommandlineMode:
return _commandLineHistory;
default:
@@ -691,7 +671,10 @@ namespace winrt::TerminalApp::implementation
{
if (const auto tabPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::TabPaletteItem>() })
{
_SwitchToTabRequestedHandlers(*this, tabPaletteItem.Tab());
if (const auto tab{ tabPaletteItem.Tab() })
{
_SwitchToTabRequestedHandlers(*this, tab);
}
}
}
}
@@ -853,9 +836,9 @@ namespace winrt::TerminalApp::implementation
return _filteredActions;
}
void CommandPalette::SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings)
void CommandPalette::SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap)
{
_bindings = bindings;
_keymap = keymap;
}
void CommandPalette::SetCommands(Collections::IVector<Command> const& actions)
@@ -868,31 +851,40 @@ namespace winrt::TerminalApp::implementation
_allCommands.Append(filteredCommand);
}
_updateFilteredActions();
if (Visibility() == Visibility::Visible && _currentMode == CommandPaletteMode::ActionMode)
{
_updateFilteredActions();
}
}
void CommandPalette::SetTabs(Collections::IVector<TabBase> const& tabs, const bool clearList)
// Method Description:
// - Replaces a list of filtered commands in the target collection with new
// commands based on the tabs in the source collection.
// Although the source observable we still don't register on it,
// so the palette user will need to reset the binding manually every time
// the source collection changes
// Arguments:
// - source: the tabs to use for creation filtered commands
// - target: the collection to store newly created filtered commands
// Return Value:
// - <none>
void CommandPalette::_bindTabs(
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& source,
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> const& target)
{
_tabActions.Clear();
for (const auto& tab : tabs)
target.Clear();
for (const auto& tab : source)
{
auto tabPaletteItem{ winrt::make<winrt::TerminalApp::implementation::TabPaletteItem>(tab) };
auto filteredCommand{ winrt::make<FilteredCommand>(tabPaletteItem) };
_tabActions.Append(filteredCommand);
target.Append(filteredCommand);
}
}
// The smooth remove/add animations that happen during
// UpdateFilteredActions don't work very well with changing the tab
// order, because of the sheer amount of remove/adds. So, let's just
// clear & rebuild the list when we change the set of tabs.
//
// Some callers might actually want smooth updating, like when the list
// of tabs changes.
if (clearList && _currentMode == CommandPaletteMode::TabSwitchMode)
{
_filteredActions.Clear();
}
_updateFilteredActions();
void CommandPalette::SetTabs(Collections::IObservableVector<TabBase> const& tabs, Collections::IObservableVector<TabBase> const& mruTabs)
{
_bindTabs(tabs, _tabActions);
_bindTabs(mruTabs, _mruTabActions);
}
void CommandPalette::EnableCommandPaletteMode(CommandPaletteLaunchMode const launchMode)
@@ -902,26 +894,11 @@ namespace winrt::TerminalApp::implementation
CommandPaletteMode::ActionMode;
_switchToMode(mode);
_updateFilteredActions();
}
void CommandPalette::_switchToMode(CommandPaletteMode mode)
{
// The smooth remove/add animations that happen during
// UpdateFilteredActions don't work very well when switching between
// modes because of the sheer amount of remove/adds. So, let's just
// clear + append when switching between modes.
if (mode != _currentMode)
{
_currentMode = mode;
_filteredActions.Clear();
auto commandsToFilter = _commandsToFilter();
for (auto action : commandsToFilter)
{
_filteredActions.Append(action);
}
}
_currentMode = mode;
ParsedCommandLineText(L"");
_searchBox().Text(L"");
@@ -954,6 +931,13 @@ namespace winrt::TerminalApp::implementation
PrefixCharacter(L">");
break;
}
// The smooth remove/add animations that happen during
// UpdateFilteredActions don't work very well when switching between
// modes because of the sheer amount of remove/adds. So, let's just
// clear + append when switching between modes.
_filteredActions.Clear();
_updateFilteredActions();
}
// Method Description:
@@ -970,27 +954,34 @@ namespace winrt::TerminalApp::implementation
winrt::hstring searchText{ _getTrimmedInput() };
auto commandsToFilter = _commandsToFilter();
for (const auto& action : commandsToFilter)
{
// Update filter for all commands
// This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting).
// Pay attention that it already updates the highlighting in the UI
action.UpdateFilter(searchText);
// if there is active search we skip commands with 0 weight
if (searchText.empty() || action.Weight() > 0)
if (_currentMode == CommandPaletteMode::TabSwitchMode)
{
std::copy(begin(commandsToFilter), end(commandsToFilter), std::back_inserter(actions));
}
else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode)
{
for (const auto& action : commandsToFilter)
{
actions.push_back(action);
// Update filter for all commands
// This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting).
// Pay attention that it already updates the highlighting in the UI
action.UpdateFilter(searchText);
// if there is active search we skip commands with 0 weight
if (searchText.empty() || action.Weight() > 0)
{
actions.push_back(action);
}
}
}
// We want to present the commands sorted,
// unless we are in the TabSwitcherMode and TabSearchMode,
// in which we want to preserve the original order (to be aligned with the tab view)
if (_currentMode != CommandPaletteMode::TabSearchMode && _currentMode != CommandPaletteMode::TabSwitchMode && _currentMode != CommandPaletteMode::CommandlineMode)
// We want to present the commands sorted
if (_currentMode == CommandPaletteMode::ActionMode)
{
std::sort(actions.begin(), actions.end(), FilteredCommand::Compare);
}
return actions;
}
@@ -1065,20 +1056,18 @@ namespace winrt::TerminalApp::implementation
_currentNestedCommands.Clear();
}
void CommandPalette::EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx)
void CommandPalette::EnableTabSwitcherMode(const uint32_t startIdx, TabSwitcherMode tabSwitcherMode)
{
_switcherStartIdx = startIdx;
if (searchMode)
{
_switchToMode(CommandPaletteMode::TabSearchMode);
}
else
{
_switchToMode(CommandPaletteMode::TabSwitchMode);
}
_updateFilteredActions();
// The _switcherStartIdx field allows us to select the current tab
// We need to take it into account only in the in-order mode,
// as an MRU mode the current tab is on top by definition.
_switcherStartIdx = tabSwitcherMode == TabSwitcherMode::InOrder ? startIdx : 0;
_tabSwitcherMode = tabSwitcherMode;
_switchToMode(CommandPaletteMode::TabSwitchMode);
}
void CommandPalette::EnableTabSearchMode()
{
_switchToMode(CommandPaletteMode::TabSearchMode);
}
}

View File

@@ -5,7 +5,7 @@
#include "FilteredCommand.h"
#include "CommandPalette.g.h"
#include "../CommandlineArgs/AppCommandlineArgs.h"
#include "AppCommandlineArgs.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
// fwdecl unittest classes
@@ -31,10 +31,8 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
void SetCommands(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& actions);
void SetTabs(Windows::Foundation::Collections::IVector<winrt::TerminalApp::TabBase> const& tabs, const bool clearList);
void SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings);
void EnableCommandPaletteMode(Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode const launchMode);
void SetTabs(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& tabs, Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& mruTabs);
void SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap);
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
@@ -45,9 +43,9 @@ namespace winrt::TerminalApp::implementation
void ScrollToTop();
void ScrollToBottom();
// Tab Switcher
void EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx);
void SetTabSwitchOrder(const Microsoft::Terminal::Settings::Model::TabSwitcherMode order);
void EnableCommandPaletteMode(Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode const launchMode);
void EnableTabSwitcherMode(const uint32_t startIdx, Microsoft::Terminal::Settings::Model::TabSwitcherMode tabSwitcherMode);
void EnableTabSearchMode();
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers);
@@ -77,8 +75,7 @@ namespace winrt::TerminalApp::implementation
Windows::UI::Xaml::RoutedEventArgs const& args);
void _previewKeyDownHandler(Windows::Foundation::IInspectable const& sender,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _keyDownHandler(Windows::Foundation::IInspectable const& sender,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _keyUpHandler(Windows::Foundation::IInspectable const& sender,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
@@ -109,11 +106,15 @@ namespace winrt::TerminalApp::implementation
std::wstring _getTrimmedInput();
void _evaluatePrefix();
Microsoft::Terminal::TerminalControl::IKeyBindings _bindings;
Microsoft::Terminal::Settings::Model::KeyMapping _keymap{ nullptr };
// Tab Switcher
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _tabActions{ nullptr };
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _mruTabActions{ nullptr };
Microsoft::Terminal::Settings::Model::TabSwitcherMode _tabSwitcherMode;
uint32_t _switcherStartIdx;
void _bindTabs(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& source, Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> const& target);
void _anchorKeyUpHandler();
winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker;
@@ -130,7 +131,7 @@ namespace winrt::TerminalApp::implementation
static constexpr int CommandLineHistoryLength = 10;
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _commandLineHistory{ nullptr };
::CommandlineArgs::AppCommandlineArgs _appArgs;
::TerminalApp::AppCommandlineArgs _appArgs;
friend class TerminalAppLocalTests::TabTests;
};

View File

@@ -22,13 +22,16 @@ namespace TerminalApp
Windows.Foundation.Collections.IObservableVector<FilteredCommand> FilteredActions { get; };
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
void SetTabs(Windows.Foundation.Collections.IVector<TabBase> tabs, Boolean clearList);
void SetKeyBindings(Microsoft.Terminal.TerminalControl.IKeyBindings bindings);
void EnableCommandPaletteMode(Microsoft.Terminal.Settings.Model.CommandPaletteLaunchMode launchMode);
void SetTabs(Windows.Foundation.Collections.IObservableVector<TabBase> tabs, Windows.Foundation.Collections.IObservableVector<TabBase> mruTabs);
void SetKeyMap(Microsoft.Terminal.Settings.Model.KeyMapping keymap);
void SelectNextItem(Boolean moveDown);
void EnableTabSwitcherMode(Boolean searchMode, UInt32 startIdx);
void EnableCommandPaletteMode(Microsoft.Terminal.Settings.Model.CommandPaletteLaunchMode launchMode);
void EnableTabSwitcherMode(UInt32 startIdx, Microsoft.Terminal.Settings.Model.TabSwitcherMode tabSwitcherMode);
void EnableTabSearchMode();
event Windows.Foundation.TypedEventHandler<CommandPalette, TabBase> SwitchToTabRequested;
event Windows.Foundation.TypedEventHandler<CommandPalette, Microsoft.Terminal.Settings.Model.Command> DispatchCommandRequested;

View File

@@ -16,7 +16,6 @@ the MIT License. See LICENSE in the project root for license information. -->
AllowFocusOnInteraction="True"
PointerPressed="_rootPointerPressed"
PreviewKeyDown="_previewKeyDownHandler"
KeyDown="_keyDownHandler"
PreviewKeyUp="_keyUpHandler"
LostFocus="_lostFocusHandler"
mc:Ignorable="d"
@@ -34,6 +33,7 @@ the MIT License. See LICENSE in the project root for license information. -->
<local:EmptyStringVisibilityConverter x:Key="ParsedCommandLineTextVisibilityConverter"/>
<local:EmptyStringVisibilityConverter x:Key="ParentCommandVisibilityConverter"/>
<local:HasNestedCommandsVisibilityConverter x:Key="HasNestedCommandsVisibilityConverter"/>
<local:HasNestedCommandsHelpTextConverter x:Key="HasNestedCommandsHelpTextConverter"/>
<model:IconPathConverter x:Key="IconSourceConverter"/>
<ResourceDictionary.ThemeDictionaries>
@@ -132,7 +132,7 @@ the MIT License. See LICENSE in the project root for license information. -->
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock"/>
<!-- ParsedCommandLineText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="ParsedCommandLineBoderStyle" TargetType="Border"/>
<Style x:Key="ParsedCommandLineBorderStyle" TargetType="Border"/>
<Style x:Key="ParsedCommandLineTextBlockStyle" TargetType="TextBlock"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
@@ -278,7 +278,6 @@ the MIT License. See LICENSE in the project root for license information. -->
AllowDrop="False"
IsItemClickEnabled="True"
ItemClick="_listItemClicked"
PreviewKeyDown="_keyDownHandler"
ItemsSource="{x:Bind FilteredActions}">
<ItemsControl.ItemTemplate >
@@ -287,9 +286,9 @@ the MIT License. See LICENSE in the project root for license information. -->
<!-- This HorizontalContentAlignment="Stretch" is important
to make sure it takes the entire width of the line -->
<ListViewItem HorizontalContentAlignment="Stretch"
IsTabStop="False"
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}">
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}"
AutomationProperties.HelpText="{x:Bind Item, Mode=OneWay, Converter={StaticResource HasNestedCommandsHelpTextConverter}}">
<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
<Grid.ColumnDefinitions>
@@ -314,7 +313,13 @@ the MIT License. See LICENSE in the project root for license information. -->
<!-- The block for the key chord is only visible
when there's actual text set as the label. See
CommandKeyChordVisibilityConverter for details. -->
CommandKeyChordVisibilityConverter for details.
We're setting the accessibility view on the
border and text block to Raw because otherwise,
Narrator will read out the key chord. Problem is,
it already did that because it was the list item's
"AcceleratorKey". It's redundant. -->
<Border
Grid.Column="2"
Visibility="{x:Bind Item.KeyChordText,
@@ -323,12 +328,14 @@ the MIT License. See LICENSE in the project root for license information. -->
Style="{ThemeResource KeyChordBorderStyle}"
Padding="2,0,2,0"
HorizontalAlignment="Right"
VerticalAlignment="Center">
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw">
<TextBlock
Style="{ThemeResource KeyChordTextBlockStyle}"
FontSize="12"
Text="{x:Bind Item.KeyChordText, Mode=OneWay}" />
Text="{x:Bind Item.KeyChordText, Mode=OneWay}"
AutomationProperties.AccessibilityView="Raw" />
</Border>
<!-- xE70E is ChevronUp. Rotated 90 degrees, it's _ChevronRight_ -->

View File

@@ -4,7 +4,7 @@
#include "pch.h"
#include "Commandline.h"
using namespace CommandlineArgs;
using namespace TerminalApp;
size_t Commandline::Argc() const
{

View File

@@ -23,12 +23,12 @@ namespace TerminalAppLocalTests
{
class CommandlineTest;
};
namespace CommandlineArgs
namespace TerminalApp
{
class Commandline;
};
class CommandlineArgs::Commandline
class TerminalApp::Commandline
{
public:
static constexpr std::wstring_view Delimiter{ L";" };

View File

@@ -1,6 +1,9 @@
#include "pch.h"
#include "HasNestedCommandsVisibilityConverter.h"
#include "HasNestedCommandsVisibilityConverter.g.cpp"
#include "HasNestedCommandsHelpTextConverter.g.cpp"
#include "LibraryResources.h"
using namespace winrt::Windows;
using namespace winrt::Windows::UI::Xaml;
@@ -36,4 +39,23 @@ namespace winrt::TerminalApp::implementation
{
throw hresult_not_implemented();
}
Foundation::IInspectable HasNestedCommandsHelpTextConverter::Convert(Foundation::IInspectable const& value,
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
Foundation::IInspectable const& /* parameter */,
hstring const& /* language */)
{
const auto paletteItem{ value.try_as<winrt::TerminalApp::ActionPaletteItem>() };
const auto hasNestedCommands = paletteItem && paletteItem.Command().HasNestedCommands();
return winrt::box_value(hasNestedCommands ? RS_(L"CommandPalette_MoreOptions/[using:Windows.UI.Xaml.Automation]AutomationProperties/HelpText") : L"");
}
// unused for one-way bindings
Foundation::IInspectable HasNestedCommandsHelpTextConverter::ConvertBack(Foundation::IInspectable const& /* value */,
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
Foundation::IInspectable const& /* parameter */,
hstring const& /* language */)
{
throw hresult_not_implemented();
}
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "HasNestedCommandsVisibilityConverter.g.h"
#include "HasNestedCommandsHelpTextConverter.g.h"
#include "../inc/cppwinrt_utils.h"
namespace winrt::TerminalApp::implementation
@@ -19,9 +20,25 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::IInspectable const& parameter,
hstring const& language);
};
struct HasNestedCommandsHelpTextConverter : HasNestedCommandsHelpTextConverterT<HasNestedCommandsHelpTextConverter>
{
HasNestedCommandsHelpTextConverter() = default;
Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value,
Windows::UI::Xaml::Interop::TypeName const& targetType,
Windows::Foundation::IInspectable const& parameter,
hstring const& language);
Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value,
Windows::UI::Xaml::Interop::TypeName const& targetType,
Windows::Foundation::IInspectable const& parameter,
hstring const& language);
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(HasNestedCommandsVisibilityConverter);
BASIC_FACTORY(HasNestedCommandsHelpTextConverter);
}

View File

@@ -16,4 +16,9 @@ namespace TerminalApp
HasNestedCommandsVisibilityConverter();
};
runtimeclass HasNestedCommandsHelpTextConverter : [default] Windows.UI.Xaml.Data.IValueConverter
{
HasNestedCommandsHelpTextConverter();
};
}

View File

@@ -10,6 +10,9 @@
#include "MinMaxCloseControl.h"
#include "MinMaxCloseControl.g.cpp"
#include <LibraryResources.h>
using namespace winrt::Windows::UI::Xaml;
namespace winrt::TerminalApp::implementation
@@ -77,6 +80,7 @@ namespace winrt::TerminalApp::implementation
MinimizeButton().Height(maximizedHeight);
MaximizeButton().Height(maximizedHeight);
CloseButton().Height(maximizedHeight);
MaximizeToolTip().Text(RS_(L"WindowRestoreDownButtonToolTip"));
break;
case WindowVisualState::WindowVisualStateNormal:
@@ -87,6 +91,7 @@ namespace winrt::TerminalApp::implementation
MinimizeButton().Height(windowedHeight);
MaximizeButton().Height(windowedHeight);
CloseButton().Height(windowedHeight);
MaximizeToolTip().Text(RS_(L"WindowMaximizeButtonToolTip"));
break;
}
}

View File

@@ -22,9 +22,11 @@ the MIT License. See LICENSE in the project root for license information. -->
<StaticResource x:Key="CaptionButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush"/>
<StaticResource x:Key="CaptionButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush"/>
<StaticResource x:Key="CaptionButtonStroke" ResourceKey="SystemControlForegroundBaseHighBrush"/>
<StaticResource x:Key="CaptionButtonStrokeColor" ResourceKey="SystemBaseHighColor"/>
<StaticResource x:Key="CaptionButtonStrokePointerOver" ResourceKey="SystemControlForegroundBaseHighBrush"/>
<StaticResource x:Key="CaptionButtonStrokePressed" ResourceKey="SystemControlForegroundBaseHighBrush"/>
<SolidColorBrush x:Key="CaptionButtonBackground" Color="Transparent" />
<Color x:Key="CaptionButtonBackgroundColor">Transparent</Color>
<SolidColorBrush x:Key="CloseButtonBackgroundPointerOver" Color="#e81123"/>
<SolidColorBrush x:Key="CloseButtonStrokePointerOver" Color="White"/>
<SolidColorBrush x:Key="CloseButtonBackgroundPressed" Color="#f1707a"/>
@@ -35,9 +37,11 @@ the MIT License. See LICENSE in the project root for license information. -->
<StaticResource x:Key="CaptionButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush"/>
<StaticResource x:Key="CaptionButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush"/>
<StaticResource x:Key="CaptionButtonStroke" ResourceKey="SystemControlForegroundBaseHighBrush"/>
<StaticResource x:Key="CaptionButtonStrokeColor" ResourceKey="SystemBaseHighColor"/>
<StaticResource x:Key="CaptionButtonStrokePointerOver" ResourceKey="SystemControlForegroundBaseHighBrush"/>
<StaticResource x:Key="CaptionButtonStrokePressed" ResourceKey="SystemControlForegroundBaseHighBrush"/>
<SolidColorBrush x:Key="CaptionButtonBackground" Color="Transparent" />
<Color x:Key="CaptionButtonBackgroundColor">Transparent</Color>
<SolidColorBrush x:Key="CloseButtonBackgroundPointerOver" Color="#e81123"/>
<SolidColorBrush x:Key="CloseButtonStrokePointerOver" Color="White"/>
<SolidColorBrush x:Key="CloseButtonBackgroundPressed" Color="#f1707a"/>
@@ -46,9 +50,11 @@ the MIT License. See LICENSE in the project root for license information. -->
<ResourceDictionary x:Key="HighContrast">
<x:Double x:Key="CaptionButtonStrokeWidth">3.0</x:Double>
<SolidColorBrush x:Key="CaptionButtonBackground" Color="{ThemeResource SystemColorButtonFaceColor}"/>
<StaticResource x:Key="CaptionButtonBackgroundColor" ResourceKey="SystemColorButtonFaceColor"/>
<SolidColorBrush x:Key="CaptionButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}"/>
<SolidColorBrush x:Key="CaptionButtonBackgroundPressed" Color="{ThemeResource SystemColorHighlightColor}"/>
<SolidColorBrush x:Key="CaptionButtonStroke" Color="{ThemeResource SystemColorButtonTextColor}"/>
<StaticResource x:Key="CaptionButtonStrokeColor" ResourceKey="SystemColorButtonTextColor"/>
<SolidColorBrush x:Key="CaptionButtonStrokePointerOver" Color="{ThemeResource SystemColorHighlightTextColor}"/>
<SolidColorBrush x:Key="CaptionButtonStrokePressed" Color="{ThemeResource SystemColorHighlightTextColor}"/>
<SolidColorBrush x:Key="CloseButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}"/>
@@ -95,8 +101,8 @@ the MIT License. See LICENSE in the project root for license information. -->
<VisualStateGroup.Transitions>
<VisualTransition From="PointerOver" To="Normal">
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBaseElement" Storyboard.TargetProperty="(UIElement.Background).(SolidColorBrush.Color)" To="{Binding Color, Source={ThemeResource CaptionButtonBackground}}" Duration="0:0:0.2"/>
<ColorAnimation Storyboard.TargetName="Path" Storyboard.TargetProperty="(UIElement.Stroke).(SolidColorBrush.Color)" To="{Binding Color, Source={ThemeResource CaptionButtonStroke}}" Duration="0:0:0.1"/>
<ColorAnimation Storyboard.TargetName="ButtonBaseElement" Storyboard.TargetProperty="(UIElement.Background).(SolidColorBrush.Color)" To="{ThemeResource CaptionButtonBackgroundColor}" Duration="0:0:0.2"/>
<ColorAnimation Storyboard.TargetName="Path" Storyboard.TargetProperty="(UIElement.Stroke).(SolidColorBrush.Color)" To="{ThemeResource CaptionButtonStrokeColor}" Duration="0:0:0.1"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
@@ -182,6 +188,13 @@ the MIT License. See LICENSE in the project root for license information. -->
<x:String x:Key="CaptionButtonPathWindowMaximized">M 0 2 h 8 v 8 h -8 v -8 M 2 2 v -2 h 8 v 8 h -2</x:String>
</ResourceDictionary>
</Button.Resources>
<ToolTipService.ToolTip>
<ToolTip>
<TextBlock>
<Run x:Name="MaximizeToolTip"/>
</TextBlock>
</ToolTip>
</ToolTipService.ToolTip>
</Button>
<Button Height="{StaticResource CaptionButtonHeightWindowed}" MinWidth="46.0" Width="46.0"
x:Name="CloseButton"

View File

@@ -17,11 +17,10 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
using namespace winrt::TerminalApp;
using namespace TerminalApp;
static const int PaneBorderSize = 2;
static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
static const float Half = 0.50f;
// WARNING: Don't do this! This won't work
// Duration duration{ std::chrono::milliseconds{ 200 } };
@@ -60,6 +59,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus
// Register an event with the control to have it inform us when it gains focus.
_gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
// When our border is tapped, make sure to transfer focus to our control.
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
@@ -368,15 +368,18 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
auto paneProfile = settings.FindProfile(_profile.value());
if (paneProfile)
{
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
// We don't want to do anything if nothing is set, so check for that first
if (static_cast<int>(paneProfile.BellStyle()) != 0)
{
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
}
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Visual))
{
// Bubble this event up to app host, starting with bubbling to the hosting tab
_PaneRaiseVisualBellHandlers(nullptr);
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
{
// Audible is set, play the sound
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
}
// raise the event with the bool value corresponding to the visual flag
_PaneRaiseBellHandlers(nullptr, WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Visual));
}
}
}
@@ -395,6 +398,16 @@ void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable cons
_GotFocusHandlers(shared_from_this());
}
// Event Description:
// - Called when our control loses focus. We'll use this to trigger our LostFocus
// callback. The tab that's hosting us should have registered a callback which
// can be used to update its own internal focus state
void Pane::_ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
_LostFocusHandlers(shared_from_this());
}
// Method Description:
// - Fire our Closed event to tell our parent that we should be removed.
// Arguments:
@@ -584,6 +597,19 @@ void Pane::_FocusFirstChild()
{
if (_IsLeaf())
{
if (_root.ActualWidth() == 0 && _root.ActualHeight() == 0)
{
// When these sizes are 0, then the pane might still be in startup,
// and doesn't yet have a real size. In that case, the control.Focus
// event won't be handled until _after_ the startup events are all
// processed. This will lead to the Tab not being notified that the
// focus moved to a different Pane.
//
// In that scenario, trigger the event manually here, to correctly
// inform the Tab that we're now focused.
_GotFocusHandlers(shared_from_this());
}
_control.Focus(FocusState::Programmatic);
}
else
@@ -704,6 +730,7 @@ void Pane::_CloseChild(const bool closeFirst)
// re-attach our handler for the control's GotFocus event.
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
_lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
// If we're inheriting the "last active" state from one of our children,
// focus our control now. This should trigger our own GotFocus event.
@@ -1216,32 +1243,6 @@ void Pane::_SetupEntranceAnimation()
setupAnimation(secondSize, false);
}
// Method Description:
// - Determines whether the pane can be split
// Arguments:
// - splitType: what type of split we want to create.
// Return Value:
// - True if the pane can be split. False otherwise.
bool Pane::CanSplit(SplitState splitType)
{
if (_IsLeaf())
{
return _CanSplit(splitType);
}
if (_firstChild->_HasFocusedChild())
{
return _firstChild->CanSplit(splitType);
}
if (_secondChild->_HasFocusedChild())
{
return _secondChild->CanSplit(splitType);
}
return false;
}
// Method Description:
// - This is a helper to determine if a given Pane can be split, but without
// using the ActualWidth() and ActualHeight() methods. This is used during
@@ -1272,12 +1273,15 @@ bool Pane::CanSplit(SplitState splitType)
// - This method is highly similar to Pane::PreCalculateAutoSplit
std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> target,
SplitState splitType,
const float splitSize,
const winrt::Windows::Foundation::Size availableSpace) const
{
if (_IsLeaf())
{
if (target.get() == this)
{
const auto firstPrecent = 1.0f - splitSize;
const auto secondPercent = splitSize;
// If this pane is a leaf, and it's the pane we're looking for, use
// the available space to calculate which direction to split in.
const Size minSize = _GetMinSize();
@@ -1290,17 +1294,19 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
else if (splitType == SplitState::Vertical)
{
const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize;
const auto newWidth = widthMinusSeparator * Half;
const auto newFirstWidth = widthMinusSeparator * firstPrecent;
const auto newSecondWidth = widthMinusSeparator * secondPercent;
return { newWidth > minSize.Width };
return { newFirstWidth > minSize.Width && newSecondWidth > minSize.Width };
}
else if (splitType == SplitState::Horizontal)
{
const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize;
const auto newHeight = heightMinusSeparator * Half;
const auto newFirstHeight = heightMinusSeparator * firstPrecent;
const auto newSecondHeight = heightMinusSeparator * secondPercent;
return { newHeight > minSize.Height };
return { newFirstHeight > minSize.Height && newSecondHeight > minSize.Height };
}
}
else
@@ -1329,8 +1335,8 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
(availableSpace.Height - firstHeight) - PaneBorderSize :
availableSpace.Height;
const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, { firstWidth, firstHeight });
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, { secondWidth, secondHeight });
const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, splitSize, { firstWidth, firstHeight });
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, splitSize, { secondWidth, secondHeight });
}
// We should not possibly be getting here - both the above branches should
@@ -1348,23 +1354,26 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
// - control: A TermControl to use in the new pane.
// Return Value:
// - The two newly created Panes
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitState splitType, const GUID& profile, const TermControl& control)
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitState splitType,
const float splitSize,
const GUID& profile,
const TermControl& control)
{
if (!_IsLeaf())
{
if (_firstChild->_HasFocusedChild())
{
return _firstChild->Split(splitType, profile, control);
return _firstChild->Split(splitType, splitSize, profile, control);
}
else if (_secondChild->_HasFocusedChild())
{
return _secondChild->Split(splitType, profile, control);
return _secondChild->Split(splitType, splitSize, profile, control);
}
return { nullptr, nullptr };
}
return _Split(splitType, profile, control);
return _Split(splitType, splitSize, profile, control);
}
// Method Description:
@@ -1392,45 +1401,6 @@ SplitState Pane::_convertAutomaticSplitState(const SplitState& splitType) const
return splitType;
}
// Method Description:
// - Determines whether the pane can be split.
// Arguments:
// - splitType: what type of split we want to create.
// Return Value:
// - True if the pane can be split. False otherwise.
bool Pane::_CanSplit(SplitState splitType)
{
const Size actualSize{ gsl::narrow_cast<float>(_root.ActualWidth()),
gsl::narrow_cast<float>(_root.ActualHeight()) };
const Size minSize = _GetMinSize();
auto actualSplitType = _convertAutomaticSplitState(splitType);
if (actualSplitType == SplitState::None)
{
return false;
}
if (actualSplitType == SplitState::Vertical)
{
const auto widthMinusSeparator = actualSize.Width - CombinedPaneBorderSize;
const auto newWidth = widthMinusSeparator * Half;
return newWidth > minSize.Width;
}
if (actualSplitType == SplitState::Horizontal)
{
const auto heightMinusSeparator = actualSize.Height - CombinedPaneBorderSize;
const auto newHeight = heightMinusSeparator * Half;
return newHeight > minSize.Height;
}
return false;
}
// Method Description:
// - Does the bulk of the work of creating a new split. Initializes our UI,
// creates a new Pane to host the control, registers event handlers.
@@ -1440,7 +1410,10 @@ bool Pane::_CanSplit(SplitState splitType)
// - control: A TermControl to use in the new pane.
// Return Value:
// - The two newly created Panes
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState splitType, const GUID& profile, const TermControl& control)
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState splitType,
const float splitSize,
const GUID& profile,
const TermControl& control)
{
if (splitType == SplitState::None)
{
@@ -1463,9 +1436,10 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
// control telling us that it's now focused, we want it telling its new
// parent.
_gotFocusRevoker.revoke();
_lostFocusRevoker.revoke();
_splitState = actualSplitType;
_desiredSplitPosition = Half;
_desiredSplitPosition = 1.0f - splitSize;
// Remove any children we currently have. We can't add the existing
// TermControl to a new grid until we do this.
@@ -1576,9 +1550,9 @@ void Pane::Restore(std::shared_ptr<Pane> zoomedPane)
// otherwise the ID value will not make sense (leaves have IDs, parents do not)
// Return Value:
// - The ID of this pane
uint16_t Pane::Id() noexcept
std::optional<uint16_t> Pane::Id() noexcept
{
return _id.value();
return _id;
}
// Method Description:
@@ -2112,4 +2086,5 @@ std::optional<SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane
}
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DEFINE_EVENT(Pane, PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);

View File

@@ -58,14 +58,16 @@ public:
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool CanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
const GUID& profile,
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
const winrt::Windows::Foundation::Size parentSize) const;
std::optional<bool> PreCalculateCanSplit(const std::shared_ptr<Pane> target,
winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
const winrt::Windows::Foundation::Size availableSpace) const;
void Shutdown();
void Close();
@@ -75,13 +77,14 @@ public:
void Maximize(std::shared_ptr<Pane> zoomedPane);
void Restore(std::shared_ptr<Pane> zoomedPane);
uint16_t Id() noexcept;
std::optional<uint16_t> Id() noexcept;
void Id(uint16_t id) noexcept;
void FocusPane(const uint16_t id);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
private:
struct SnapSizeResult;
@@ -109,6 +112,7 @@ private:
winrt::event_token _warningBellToken{ 0 };
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
std::shared_mutex _createCloseLock{};
@@ -120,8 +124,8 @@ private:
bool _HasFocusedChild() const noexcept;
void _SetupChildCloseHandlers();
bool _CanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
const GUID& profile,
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
@@ -142,6 +146,8 @@ private:
winrt::Windows::Foundation::IInspectable const& e);
void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void _ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;

View File

@@ -226,6 +226,9 @@
<value>&#x2022; Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
<comment>{Locked="\"keys\"","&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="FailedToParseSubCommands" xml:space="preserve">
<value>&#x2022; Failed to parse all subcommands of nested command.</value>
</data>
<data name="MissingRequiredParameter" xml:space="preserve">
<value>&#x2022; Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
@@ -249,6 +252,14 @@
<value>Found a command with an invalid "colorScheme". This command will be ignored. Make sure that when setting a "colorScheme", the value matches the "name" of a color scheme in the "schemes" list.</value>
<comment>{Locked="\"colorScheme\"","\"name\"","\"schemes\""}</comment>
</data>
<data name="InvalidSplitSize" xml:space="preserve">
<value>Found a "splitPane" command with an invalid "size". This command will be ignored. Make sure the size is between 0 and 1, exclusive.</value>
<comment>{Locked="\"splitPane\"","\"size\""}</comment>
</data>
<data name="FailedToParseStartupActions" xml:space="preserve">
<value>Failed to parse "startupActions".</value>
<comment>{Locked="\"startupActions\""}</comment>
</data>
<data name="CmdCommandArgDesc" xml:space="preserve">
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
</data>
@@ -268,6 +279,9 @@
<data name="CmdFocusTabTargetArgDesc" xml:space="preserve">
<value>Move focus the tab at the given index</value>
</data>
<data name="CmdSplitPaneSizeArgDesc" xml:space="preserve">
<value>Specify the size as a percentage of the parent pane. Valid values are between (0,1), exclusive.</value>
</data>
<data name="CmdNewTabDesc" xml:space="preserve">
<value>Create a new tab</value>
</data>
@@ -311,6 +325,16 @@
<data name="CmdFullscreenDesc" xml:space="preserve">
<value>Launch the window in fullscreen mode</value>
</data>
<data name="CmdMoveFocusDesc" xml:space="preserve">
<value>Move focus to the adjacent pane in the specified direction</value>
</data>
<data name="CmdMFDesc" xml:space="preserve">
<value>An alias for the "move-focus" subcommand.</value>
<comment>{Locked="\"move-focus\""}</comment>
</data>
<data name="CmdMoveFocusDirectionArgDesc" xml:space="preserve">
<value>The direction to move focus in</value>
</data>
<data name="CmdFocusDesc" xml:space="preserve">
<value>Launch the window in focus mode</value>
</data>
@@ -335,9 +359,6 @@
<data name="WindowMaximizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Maximize</value>
</data>
<data name="WindowMaximizeButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Maximize</value>
</data>
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Minimize</value>
</data>
@@ -398,7 +419,7 @@
<data name="MultiLinePasteDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="MultiLinePasteDialog.Content" xml:space="preserve">
<data name="MultiLineWarningText.Text" xml:space="preserve">
<value>You are about to paste text that contains multiple lines. If you paste this text into your shell, it may result in the unexpected execution of commands. Do you wish to continue?</value>
</data>
<data name="MultiLinePasteDialog.PrimaryButtonText" xml:space="preserve">
@@ -415,7 +436,7 @@
</data>
<data name="CommandPalette_ParsedCommandLine" xml:space="preserve">
<value>Executing command line will invoke the following commands:</value>
<comment>Will be followed by a list of strings describing parsed commands</comment>
<comment>Will be followed by a list of strings describing parsed commands</comment>
</data>
<data name="CommandPalette_FailedParsingCommandLine" xml:space="preserve">
<value>Failed parsing command line:</value>
@@ -520,4 +541,16 @@
<data name="NoticeWarning" xml:space="preserve">
<value>Warning</value>
</data>
</root>
<data name="ClipboardTextHeader.Text" xml:space="preserve">
<value>Clipboard contents (preview):</value>
</data>
<data name="CommandPalette_MoreOptions.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
<value>More options</value>
</data>
<data name="WindowMaximizeButtonToolTip" xml:space="preserve">
<value>Maximize</value>
</data>
<data name="WindowRestoreDownButtonToolTip" xml:space="preserve">
<value>Restore Down</value>
</data>
</root>

View File

@@ -22,6 +22,11 @@ namespace winrt::TerminalApp::implementation
// - true if we handled the event was handled, else false.
bool ShortcutActionDispatch::DoAction(const ActionAndArgs& actionAndArgs)
{
if (!actionAndArgs)
{
return false;
}
const auto& action = actionAndArgs.Action();
const auto& args = actionAndArgs.Args();
auto eventArgs = args ? ActionEventArgs{ args } :

View File

@@ -49,7 +49,7 @@ namespace winrt::TerminalApp::implementation
Controls::MenuFlyoutItem closeTabMenuItem;
Controls::FontIcon closeSymbol;
closeSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
closeSymbol.Glyph(L"\xE8BB");
closeSymbol.Glyph(L"\xE711");
closeTabMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
@@ -62,7 +62,6 @@ namespace winrt::TerminalApp::implementation
// Build the menu
Controls::MenuFlyout newTabFlyout;
newTabFlyout.Items().Append(_CreateCloseSubMenu());
newTabFlyout.Items().Append(closeTabMenuItem);
TabViewItem().ContextFlyout(newTabFlyout);
}
@@ -138,10 +137,88 @@ namespace winrt::TerminalApp::implementation
TabViewIndex(idx);
TabViewNumTabs(numTabs);
_EnableCloseMenuItems();
_UpdateSwitchToTabKeyChord();
}
void TabBase::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
{
_dispatch = dispatch;
}
void TabBase::SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap)
{
_keymap = keymap;
_UpdateSwitchToTabKeyChord();
}
// Method Description:
// - Sets the key chord resulting in switch to the current tab.
// Updates tool tip if required
// Arguments:
// - keyChord - string representation of the key chord that switches to the current tab
// Return Value:
// - <none>
winrt::fire_and_forget TabBase::_UpdateSwitchToTabKeyChord()
{
SwitchToTabArgs args{ _TabViewIndex };
ActionAndArgs switchToTab{ ShortcutAction::SwitchToTab, args };
const auto keyChord = _keymap ? _keymap.GetKeyBindingForActionWithArgs(switchToTab) : nullptr;
const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L"";
if (_keyChord == keyChordText)
{
return;
}
_keyChord = keyChordText;
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
if (auto tab{ weakThis.get() })
{
_UpdateToolTip();
}
}
// Method Description:
// - Creates a text for the title run in the tool tip by returning tab title
// Arguments:
// - <none>
// Return Value:
// - The value to populate in the title run of the tool tip
winrt::hstring TabBase::_CreateToolTipTitle()
{
return _Title;
}
// Method Description:
// - Sets tab tool tip to a concatenation of title and key chord
// Arguments:
// - <none>
// Return Value:
// - <none>
void TabBase::_UpdateToolTip()
{
auto titleRun = WUX::Documents::Run();
titleRun.Text(_CreateToolTipTitle());
auto textBlock = WUX::Controls::TextBlock{};
textBlock.TextAlignment(WUX::TextAlignment::Center);
textBlock.Inlines().Append(titleRun);
if (!_keyChord.empty())
{
auto keyChordRun = WUX::Documents::Run();
keyChordRun.Text(_keyChord);
keyChordRun.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(keyChordRun);
}
WUX::Controls::ToolTip toolTip{};
toolTip.Content(textBlock);
WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip);
}
}

View File

@@ -23,6 +23,7 @@ namespace winrt::TerminalApp::implementation
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs);
void SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
@@ -43,12 +44,18 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{};
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{};
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
Microsoft::Terminal::Settings::Model::KeyMapping _keymap{ nullptr };
winrt::hstring _keyChord{};
virtual void _CreateContextMenu();
virtual winrt::hstring _CreateToolTipTitle();
winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _CreateCloseSubMenu();
void _EnableCloseMenuItems();
void _CloseTabsAfter();
void _CloseOtherTabs();
winrt::fire_and_forget _UpdateSwitchToTabKeyChord();
void _UpdateToolTip();
friend class ::TerminalAppLocalTests::TabTests;
};

View File

@@ -65,7 +65,7 @@ namespace winrt::TerminalApp::implementation
"TabRenamerOpened",
TraceLoggingDescription("Event emitted when the tab renamer is opened"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
// Method Description:
@@ -75,6 +75,14 @@ namespace winrt::TerminalApp::implementation
void TabHeaderControl::RenameBoxLostFocusHandler(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::RoutedEventArgs const& /*e*/)
{
// If the context menu associated with the renamer text box is open we know it gained the focus.
// In this case we ignore this event (we will regain the focus once the menu will be closed).
const auto flyout = HeaderRenamerTextBox().ContextFlyout();
if (flyout && flyout.IsOpen())
{
return;
}
// Log the data here, rather than in _CloseRenameBox. If we do it there,
// it'll get fired twice, once when the key is pressed to commit/cancel,
// and then again when the focus is lost
@@ -85,7 +93,7 @@ namespace winrt::TerminalApp::implementation
TraceLoggingDescription("Event emitted when the tab renamer is closed"),
TraceLoggingBoolean(_renameCancelled, "CancelledRename", "True if the user cancelled the rename, false if they committed."),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
_CloseRenameBox();
if (!_renameCancelled)
@@ -98,7 +106,11 @@ namespace winrt::TerminalApp::implementation
// - Hides the rename box and displays the title text block
void TabHeaderControl::_CloseRenameBox()
{
HeaderRenamerTextBox().Visibility(Windows::UI::Xaml::Visibility::Collapsed);
HeaderTextBlock().Visibility(Windows::UI::Xaml::Visibility::Visible);
if (HeaderRenamerTextBox().Visibility() == Windows::UI::Xaml::Visibility::Visible)
{
HeaderRenamerTextBox().Visibility(Windows::UI::Xaml::Visibility::Collapsed);
HeaderTextBlock().Visibility(Windows::UI::Xaml::Visibility::Visible);
_RenameEndedHandlers(*this, nullptr);
}
}
}

View File

@@ -23,10 +23,14 @@ namespace winrt::TerminalApp::implementation
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(bool, IsPaneZoomed, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(double, RenamerMaxWidth, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(bool, IsProgressRingActive, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(bool, IsProgressRingIndeterminate, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(bool, BellIndicator, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(uint32_t, ProgressValue, _PropertyChangedHandlers);
TYPED_EVENT(RenameEnded, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
bool _receivedKeyDown{ false };
bool _renameCancelled{ false };

View File

@@ -9,13 +9,16 @@ namespace TerminalApp
{
String Title { get; set; };
Boolean IsPaneZoomed { get; set; };
Double RenamerMaxWidth { get; set; };
Boolean IsProgressRingActive { get; set; };
Boolean IsProgressRingIndeterminate { get; set; };
Boolean BellIndicator { get; set; };
UInt32 ProgressValue { get; set; };
TabHeaderControl();
void BeginRename();
event TitleChangeRequestedArgs TitleChangeRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> RenameEnded;
}
}

View File

@@ -9,8 +9,25 @@ the MIT License. See LICENSE in the project root for license information. -->
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
mc:Ignorable="d"
MinHeight="16">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0,0,0,1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0,0,0,1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0,0,0,1</Thickness>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel x:Name="HeaderStackPanel"
Orientation="Horizontal">
<mux:ProgressRing x:Name="HeaderProgressRing"
@@ -26,6 +43,12 @@ the MIT License. See LICENSE in the project root for license information. -->
<!--We want the progress ring to 'replace' the tab icon, but we don't have control
over the tab icon here (the tab view item does) - so we hide the tab icon there
and use a negative margin for the progress ring here to put it where the icon would be-->
<FontIcon x:Name="HeaderBellIndicator"
FontFamily="Segoe MDL2 Assets"
Visibility="{x:Bind BellIndicator, Mode=OneWay}"
Glyph="&#xEA8F;"
FontSize="12"
Margin="0,0,8,0"/>
<FontIcon x:Name="HeaderZoomIcon"
FontFamily="Segoe MDL2 Assets"
Visibility="{x:Bind IsPaneZoomed, Mode=OneWay}"
@@ -39,8 +62,11 @@ the MIT License. See LICENSE in the project root for license information. -->
Visibility="Collapsed"
MinHeight="0"
Padding="4,0,4,0"
Margin="0,-8,0,-8"
MaxLength="1024"
LostFocus="RenameBoxLostFocusHandler"/>
LostFocus="RenameBoxLostFocusHandler"
Height="16"
FontSize="12"
IsSpellCheckEnabled="False"
MaxWidth="{x:Bind RenamerMaxWidth, Mode=OneWay}"/>
</StackPanel>
</UserControl>

View File

@@ -19,7 +19,7 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::TerminalApp::implementation
{
TabPaletteItem::TabPaletteItem(winrt::TerminalApp::TabBase const& tab) :
_Tab(tab)
_tab(tab)
{
Name(tab.Title());
Icon(tab.Icon());

View File

@@ -14,9 +14,13 @@ namespace winrt::TerminalApp::implementation
TabPaletteItem() = default;
TabPaletteItem(winrt::TerminalApp::TabBase const& tab);
GETSET_PROPERTY(winrt::TerminalApp::TabBase, Tab, nullptr);
winrt::TerminalApp::TabBase Tab() const noexcept
{
return _tab.get();
}
private:
winrt::weak_ref<winrt::TerminalApp::TabBase> _tab;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _tabChangedRevoker;
};
}

View File

@@ -25,8 +25,7 @@
<ClCompile>
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
on being compiled with RTTI (/GR). We need this in this project, because
we're including the CLI11 header via the CommandlineArgs project. -->
on being compiled with RTTI (/GR). -->
<RuntimeTypeInfo>true</RuntimeTypeInfo>
</ClCompile>
</ItemDefinitionGroup>
@@ -72,6 +71,8 @@
<ItemGroup>
<ClInclude Include="ActionPaletteItem.h" />
<ClInclude Include="App.base.h" />
<ClInclude Include="AppCommandlineArgs.h" />
<ClInclude Include="Commandline.h" />
<ClInclude Include="CommandLinePaletteItem.h" />
<ClInclude Include="Jumplist.h" />
<ClInclude Include="MinMaxCloseControl.h">
@@ -143,6 +144,8 @@
<ClCompile Include="ActionPaletteItem.cpp" />
<ClCompile Include="CommandLinePaletteItem.cpp" />
<ClCompile Include="init.cpp" />
<ClCompile Include="AppCommandlineArgs.cpp" />
<ClCompile Include="Commandline.cpp" />
<ClCompile Include="Jumplist.cpp" />
<ClCompile Include="MinMaxCloseControl.cpp">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
@@ -285,14 +288,6 @@
you also update all the consumers
-->
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\CommandlineArgs\CommandlineArgs.vcxproj" >
<Private>true</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<!-- Make sure to set ReferenceOutputAssembly=false, or we'll try and
MDMERGE with MUX from the CommandlineArgs output, and get duplicate type
errors everywhere. -->
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\WinRTUtils\WinRTUtils.vcxproj">
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>

View File

@@ -4,7 +4,6 @@
#include "pch.h"
#include "TerminalPage.h"
#include "Utils.h"
#include "AppLogic.h"
#include "../../types/inc/utils.hpp"
#include <LibraryResources.h>
@@ -44,7 +43,7 @@ namespace winrt::TerminalApp::implementation
{
TerminalPage::TerminalPage() :
_tabs{ winrt::single_threaded_observable_vector<TerminalApp::TabBase>() },
_mruTabs{ winrt::single_threaded_vector<TerminalApp::TabBase>() },
_mruTabs{ winrt::single_threaded_observable_vector<TerminalApp::TabBase>() },
_startupActions{ winrt::single_threaded_vector<ActionAndArgs>() },
_hostingHwnd{}
{
@@ -108,7 +107,7 @@ namespace winrt::TerminalApp::implementation
if (auto page{ weakThis.get() })
{
_UpdateCommandsForPalette();
CommandPalette().SetKeyBindings(*_bindings);
CommandPalette().SetKeyMap(_settings.KeyMap());
}
}
@@ -136,6 +135,8 @@ namespace winrt::TerminalApp::implementation
}
CATCH_LOG();
_tabRow.PointerMoved({ this, &TerminalPage::_RestorePointerCursorHandler });
_tabView.CanReorderTabs(!isElevated);
_tabView.CanDragTabs(!isElevated);
@@ -209,6 +210,7 @@ namespace winrt::TerminalApp::implementation
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
nullptr);
}
else
@@ -253,6 +255,13 @@ namespace winrt::TerminalApp::implementation
_layoutUpdatedRevoker = _tabContent.LayoutUpdated(winrt::auto_revoke, { this, &TerminalPage::_OnFirstLayout });
_isAlwaysOnTop = _settings.GlobalSettings().AlwaysOnTop();
// Setup mouse vanish attributes
SystemParametersInfoW(SPI_GETMOUSEVANISH, 0, &_shouldMouseVanish, false);
// Store cursor, so we can restore it, e.g., after mouse vanishing
// (we'll need to adapt this logic once we make cursor context aware)
_defaultPointerCursor = CoreWindow::GetForCurrentThread().PointerCursor();
}
// Method Description:
@@ -560,6 +569,7 @@ namespace winrt::TerminalApp::implementation
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
newTerminalArgs);
}
else
@@ -600,7 +610,9 @@ namespace winrt::TerminalApp::implementation
settingsItem.Click({ this, &TerminalPage::_SettingsButtonOnClick });
newTabFlyout.Items().Append(settingsItem);
auto settingsKeyChord = keyBindings.GetKeyBindingForAction(ShortcutAction::OpenSettings);
Microsoft::Terminal::Settings::Model::OpenSettingsArgs args{ SettingsTarget::SettingsFile };
Microsoft::Terminal::Settings::Model::ActionAndArgs settingsAction{ ShortcutAction::OpenSettings, args };
const auto settingsKeyChord{ keyBindings.GetKeyBindingForActionWithArgs(settingsAction) };
if (settingsKeyChord)
{
_SetAcceleratorForMenuItem(settingsItem, settingsKeyChord);
@@ -746,6 +758,7 @@ namespace winrt::TerminalApp::implementation
_mruTabs.Append(*newTabImpl);
newTabImpl->SetDispatch(*_actionDispatch);
newTabImpl->SetKeyMap(_settings.KeyMap());
// Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command.
_UpdateTabIndices();
@@ -804,12 +817,25 @@ namespace winrt::TerminalApp::implementation
}
});
newTabImpl->TabRenamerDeactivated([weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
if (const auto page{ weakThis.get() })
{
if (!page->_newTabButton.Flyout().IsOpen())
{
if (const auto tab{ page->_GetFocusedTab() })
{
tab.Focus(FocusState::Programmatic);
}
}
}
});
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
TermControl newControl{ settings, debugConnection };
_RegisterTerminalEvents(newControl, *newTabImpl);
// Split (auto) with the debug tap.
newTabImpl->SplitPane(SplitState::Automatic, profileGuid, newControl);
newTabImpl->SplitPane(SplitState::Automatic, 0.5f, profileGuid, newControl);
}
// This kicks off TabView::SelectionChanged, in response to which
@@ -927,6 +953,56 @@ namespace winrt::TerminalApp::implementation
_ShowAboutDialog();
}
// Method Description:
// Called when the users pressed keyBindings while CommandPalette is open.
// Arguments:
// - e: the KeyRoutedEventArgs containing info about the keystroke.
// Return Value:
// - <none>
void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
winrt::Microsoft::Terminal::TerminalControl::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
const auto actionAndArgs = _settings.KeyMap().TryLookup(kc);
if (actionAndArgs)
{
if (CommandPalette().Visibility() == Visibility::Visible && actionAndArgs.Action() != ShortcutAction::ToggleCommandPalette)
{
CommandPalette().Visibility(Visibility::Collapsed);
}
_actionDispatch->DoAction(actionAndArgs);
e.Handled(true);
}
}
// Method Description:
// Handles preview key on the SUI tab, by handling close tab / next tab / previous tab
// This is a temporary solution - we need to fix all key-bindings work from SUI as long as they don't harm
// the SUI behavior
// Arguments:
// - e: the KeyRoutedEventArgs containing info about the keystroke.
// Return Value:
// - <none>
void TerminalPage::_SUIPreviewKeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
winrt::Microsoft::Terminal::TerminalControl::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
const auto actionAndArgs = _settings.KeyMap().TryLookup(kc);
if (actionAndArgs && (actionAndArgs.Action() == ShortcutAction::CloseTab || actionAndArgs.Action() == ShortcutAction::NextTab || actionAndArgs.Action() == ShortcutAction::PrevTab || actionAndArgs.Action() == ShortcutAction::ClosePane))
{
_actionDispatch->DoAction(actionAndArgs);
e.Handled(true);
}
}
// Method Description:
// - Configure the AppKeyBindings to use our ShortcutActionDispatch and the updated KeyMapping
// as the object to handle dispatching ShortcutAction events.
@@ -1004,8 +1080,7 @@ namespace winrt::TerminalApp::implementation
{
auto newTabTitle = tab.Title();
if (_settings.GlobalSettings().ShowTitleInTitlebar() &&
tab.FocusState() != FocusState::Unfocused)
if (_settings.GlobalSettings().ShowTitleInTitlebar() && tab == _GetFocusedTab())
{
_titleChangeHandlers(*this, newTabTitle);
}
@@ -1065,34 +1140,38 @@ namespace winrt::TerminalApp::implementation
// - Duplicates the current focused tab
void TerminalPage::_DuplicateTabViewItem()
{
if (auto index{ _GetFocusedTabIndex() })
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
try
{
try
{
// TODO: GH#5047 - In the future, we should get the Profile of
// the focused pane, and use that to build a new instance of the
// settings so we can duplicate this tab/pane.
//
// Currently, if the profile doesn't exist anymore in our
// settings, we'll silently do nothing.
//
// In the future, it will be preferable to just duplicate the
// current control's settings, but we can't do that currently,
// because we won't be able to create a new instance of the
// connection without keeping an instance of the original Profile
// object around.
// TODO: GH#5047 - In the future, we should get the Profile of
// the focused pane, and use that to build a new instance of the
// settings so we can duplicate this tab/pane.
//
// Currently, if the profile doesn't exist anymore in our
// settings, we'll silently do nothing.
//
// In the future, it will be preferable to just duplicate the
// current control's settings, but we can't do that currently,
// because we won't be able to create a new instance of the
// connection without keeping an instance of the original Profile
// object around.
const auto& profileGuid = terminalTab->GetFocusedProfile();
if (profileGuid.has_value())
const auto& profileGuid = terminalTab->GetFocusedProfile();
if (profileGuid.has_value())
{
const auto settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
const auto workingDirectory = terminalTab->GetActiveTerminalControl().WorkingDirectory();
const auto validWorkingDirectory = !workingDirectory.empty();
if (validWorkingDirectory)
{
const auto settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
_CreateNewTabFromSettings(profileGuid.value(), settings);
settings.StartingDirectory(workingDirectory);
}
_CreateNewTabFromSettings(profileGuid.value(), settings);
}
CATCH_LOG();
}
CATCH_LOG();
}
}
@@ -1117,6 +1196,13 @@ namespace winrt::TerminalApp::implementation
// - tabIndex: the index of the tab to be removed
void TerminalPage::_RemoveTabViewItemByIndex(uint32_t tabIndex)
{
// We use _removing flag to suppress _OnTabSelectionChanged events
// that might get triggered while removing
_removing = true;
auto unsetRemoving = wil::scope_exit([&]() noexcept { _removing = false; });
const auto focusedTabIndex{ _GetFocusedTabIndex() };
// Removing the tab from the collection should destroy its control and disconnect its connection,
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
auto tab{ _tabs.GetAt(tabIndex) };
@@ -1137,43 +1223,51 @@ namespace winrt::TerminalApp::implementation
{
_lastTabClosedHandlers(*this, nullptr);
}
else if (_isFullscreen || _rearranging || _isInFocusMode)
else if (focusedTabIndex.has_value() && focusedTabIndex.value() == gsl::narrow_cast<uint32_t>(tabIndex))
{
// GH#5799 - If we're fullscreen, the TabView isn't visible. If it's
// not Visible, it's _not_ going to raise a SelectionChanged event,
// which is what we usually use to focus another tab. Instead, we'll
// have to do it manually here.
//
// GH#7916 - Similarly, TabView isn't visible in focus mode.
// We need to focus another tab manually from the same considerations as above.
//
// GH#5559 Similarly, we suppress _OnTabItemsChanged events during a
// rearrange, so if a tab is closed while we're rearranging tabs, do
// this manually.
//
// We can't use
// auto selectedIndex = _tabView.SelectedIndex();
// Because this will always return -1 in this scenario unfortunately.
//
// So, what we're going to try to do is move the focus to the tab
// to the left, within the bounds of how many tabs we have.
//
// EX: we have 4 tabs: [A, B, C, D]. If we close:
// * A (tabIndex=0): We'll want to focus tab B (now in index 0)
// * B (tabIndex=1): We'll want to focus tab A (now in index 0)
// * C (tabIndex=2): We'll want to focus tab B (now in index 1)
// * D (tabIndex=3): We'll want to focus tab C (now in index 2)
const auto newSelectedIndex = std::clamp<int32_t>(tabIndex - 1, 0, _tabs.Size());
// _UpdatedSelectedTab will do the work of setting up the new tab as
// the focused one, and unfocusing all the others.
_UpdatedSelectedTab(newSelectedIndex);
// Manually select the new tab to get focus, rather than relying on TabView since:
// 1. We want to customize this behavior (e.g., use MRU logic)
// 2. In fullscreen (GH#5799) and focus (GH#7916) modes the _OnTabItemsChanged is not fired
// 3. When rearranging tabs (GH#7916) _OnTabItemsChanged is suppressed
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
// Also, we need to _manually_ set the SelectedItem of the tabView
// here. If we don't, then the TabView will technically not have a
// selected item at all, which can make things like ClosePane not
// work correctly.
auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) };
_tabView.SelectedItem(newSelectedTab.TabViewItem());
if (tabSwitchMode == TabSwitcherMode::MostRecentlyUsed)
{
const auto newSelectedTab = _mruTabs.GetAt(0);
uint32_t newSelectedIndex;
if (_tabs.IndexOf(newSelectedTab, newSelectedIndex))
{
_UpdatedSelectedTab(newSelectedIndex);
_tabView.SelectedItem(newSelectedTab.TabViewItem());
}
}
else
{
// We can't use
// auto selectedIndex = _tabView.SelectedIndex();
// Because this will always return -1 in this scenario unfortunately.
//
// So, what we're going to try to do is move the focus to the tab
// to the left, within the bounds of how many tabs we have.
//
// EX: we have 4 tabs: [A, B, C, D]. If we close:
// * A (tabIndex=0): We'll want to focus tab B (now in index 0)
// * B (tabIndex=1): We'll want to focus tab A (now in index 0)
// * C (tabIndex=2): We'll want to focus tab B (now in index 1)
// * D (tabIndex=3): We'll want to focus tab C (now in index 2)
const auto newSelectedIndex = std::clamp<int32_t>(tabIndex - 1, 0, _tabs.Size());
// _UpdatedSelectedTab will do the work of setting up the new tab as
// the focused one, and unfocusing all the others.
_UpdatedSelectedTab(newSelectedIndex);
// Also, we need to _manually_ set the SelectedItem of the tabView
// here. If we don't, then the TabView will technically not have a
// selected item at all, which can make things like ClosePane not
// work correctly.
auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) };
_tabView.SelectedItem(newSelectedTab.TabViewItem());
}
}
// GH#5559 - If we were in the middle of a drag/drop, end it by clearing
@@ -1211,6 +1305,9 @@ namespace winrt::TerminalApp::implementation
// Add an event handler for when the terminal wants to set a progress indicator on the taskbar
term.SetTaskbarProgress({ this, &TerminalPage::_SetTaskbarProgressHandler });
term.HidePointerCursor({ this, &TerminalPage::_HidePointerCursorHandler });
term.RestorePointerCursor({ this, &TerminalPage::_RestorePointerCursorHandler });
// Bind Tab events to the TermControl and the Tab's Pane
hostingTab.Initialize(term);
@@ -1274,57 +1371,27 @@ namespace winrt::TerminalApp::implementation
// - Sets focus to the tab to the right or left the currently selected tab.
void TerminalPage::_SelectNextTab(const bool bMoveRight)
{
if (CommandPalette().Visibility() == Visibility::Visible)
{
// If the tab switcher is currently open, don't change its mode.
// Just select the new tab.
CommandPalette().SelectNextItem(bMoveRight);
return;
}
const auto index{ _GetFocusedTabIndex().value_or(0) };
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
const bool useInOrderTabIndex = tabSwitchMode != TabSwitcherMode::MostRecentlyUsed;
// First, determine what the index of the newly selected tab should be.
// This changes if we're doing an in-order traversal vs a MRU traversal.
auto newTabIndex = 0;
if (useInOrderTabIndex)
if (tabSwitchMode == TabSwitcherMode::Disabled)
{
// Determine what the next in-order tab index is
if (auto index{ _GetFocusedTabIndex() })
{
uint32_t tabCount = _tabs.Size();
// Wraparound math. By adding tabCount and then calculating
// modulo tabCount, we clamp the values to the range [0,
// tabCount) while still supporting moving leftward from 0 to
// tabCount - 1.
newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
}
uint32_t tabCount = _tabs.Size();
// Wraparound math. By adding tabCount and then calculating
// modulo tabCount, we clamp the values to the range [0,
// tabCount) while still supporting moving leftward from 0 to
// tabCount - 1.
const auto newTabIndex = ((tabCount + index + (bMoveRight ? 1 : -1)) % tabCount);
_SelectTab(newTabIndex);
}
else
{
// Determine what the next "most recently used" index is.
// In this case, our focused tab index (in the MRU ordering) is
// always 0. So, going next should go to index 1, and going prev
// should wrap to the end.
uint32_t tabCount = _mruTabs.Size();
newTabIndex = ((tabCount + (bMoveRight ? 1 : -1)) % tabCount);
}
const bool useTabSwitcher = tabSwitchMode != TabSwitcherMode::Disabled;
if (useTabSwitcher)
{
CommandPalette().SetTabs(useInOrderTabIndex ? _tabs : _mruTabs, true);
CommandPalette().SetTabs(_tabs, _mruTabs);
// Otherwise, set up the tab switcher in the selected mode, with
// the given ordering, and make it visible.
CommandPalette().EnableTabSwitcherMode(false, newTabIndex);
CommandPalette().EnableTabSwitcherMode(index, tabSwitchMode);
CommandPalette().Visibility(Visibility::Visible);
}
else if (auto index{ _GetFocusedTabIndex() })
{
_SelectTab(newTabIndex);
CommandPalette().SelectNextItem(bMoveRight);
}
}
@@ -1373,20 +1440,17 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_UnZoomIfNeeded()
{
if (auto focusedTab = _GetFocusedTab())
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
if (activeTab->IsZoomed())
{
if (activeTab->IsZoomed())
{
// Remove the content from the tab first, so Pane::UnZoom can
// re-attach the content to the tree w/in the pane
_tabContent.Children().Clear();
// In ExitZoom, we'll change the Tab's Content(), triggering the
// content changed event, which will re-attach the tab's new content
// root to the tree.
activeTab->ExitZoom();
}
// Remove the content from the tab first, so Pane::UnZoom can
// re-attach the content to the tree w/in the pane
_tabContent.Children().Clear();
// In ExitZoom, we'll change the Tab's Content(), triggering the
// content changed event, which will re-attach the tab's new content
// root to the tree.
activeTab->ExitZoom();
}
}
}
@@ -1401,24 +1465,18 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_MoveFocus(const FocusDirection& direction)
{
if (auto index{ _GetFocusedTabIndex() })
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
_UnZoomIfNeeded();
terminalTab->NavigateFocus(direction);
}
_UnZoomIfNeeded();
terminalTab->NavigateFocus(direction);
}
}
TermControl TerminalPage::_GetActiveControl()
{
if (auto index{ _GetFocusedTabIndex() })
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
return terminalTab->GetActiveTerminalControl();
}
return terminalTab->GetActiveTerminalControl();
}
return nullptr;
}
@@ -1443,7 +1501,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - returns a com_ptr to the currently focused tab. This might return null,
// so make sure to check the result!
winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab()
winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab() const noexcept
{
if (auto index{ _GetFocusedTabIndex() })
{
@@ -1452,6 +1510,18 @@ namespace winrt::TerminalApp::implementation
return nullptr;
}
// Method Description:
// - returns a com_ptr to the currently focused tab implementation. This might return null,
// so make sure to check the result!
winrt::com_ptr<TerminalTab> TerminalPage::_GetFocusedTabImpl() const noexcept
{
if (auto tab{ _GetFocusedTab() })
{
return _GetTerminalTabImpl(tab);
}
return nullptr;
}
// Method Description:
// - An async method for changing the focused tab on the UI thread. This
// method will _only_ set the selected item of the TabView, which will
@@ -1493,12 +1563,16 @@ namespace winrt::TerminalApp::implementation
// tab's Closed event.
void TerminalPage::_CloseFocusedPane()
{
if (auto index{ _GetFocusedTabIndex() })
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
_UnZoomIfNeeded();
terminalTab->ClosePane();
}
else if (auto index{ _GetFocusedTabIndex() })
{
if (_tabs.GetAt(*index).try_as<TerminalApp::SettingsTab>())
{
_UnZoomIfNeeded();
terminalTab->ClosePane();
_RemoveTabViewItemByIndex(*index);
}
}
}
@@ -1542,26 +1616,23 @@ namespace winrt::TerminalApp::implementation
// - rowsToScroll: a number of lines to move the viewport. If not provided we will use a system default.
void TerminalPage::_Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference<uint32_t>& rowsToScroll)
{
if (auto index{ _GetFocusedTabIndex() })
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
uint32_t realRowsToScroll;
if (rowsToScroll == nullptr)
{
uint32_t realRowsToScroll;
if (rowsToScroll == nullptr)
{
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
terminalTab->GetActiveTerminalControl().GetViewHeight() :
_systemRowsToScroll;
}
else
{
// use the custom value specified in the command
realRowsToScroll = rowsToScroll.Value();
}
auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
terminalTab->Scroll(scrollDelta);
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
terminalTab->GetActiveTerminalControl().GetViewHeight() :
_systemRowsToScroll;
}
else
{
// use the custom value specified in the command
realRowsToScroll = rowsToScroll.Value();
}
auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
terminalTab->Scroll(scrollDelta);
}
}
@@ -1578,6 +1649,7 @@ namespace winrt::TerminalApp::implementation
// configurations. See CascadiaSettings::BuildSettings for more details.
void TerminalPage::_SplitPane(const SplitState splitType,
const SplitType splitMode,
const float splitSize,
const NewTerminalArgs& newTerminalArgs)
{
// Do nothing if we're requesting no split.
@@ -1586,17 +1658,9 @@ namespace winrt::TerminalApp::implementation
return;
}
auto indexOpt = _GetFocusedTabIndex();
const auto focusedTab{ _GetFocusedTabImpl() };
// Do nothing if for some reason, there's no tab in focus. We don't want to crash.
if (!indexOpt)
{
return;
}
auto focusedTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt));
// Do nothing if the focused tab isn't a TerminalTab
// Do nothing if no TerminalTab is focused
if (!focusedTab)
{
return;
@@ -1615,6 +1679,12 @@ namespace winrt::TerminalApp::implementation
{
profileFound = true;
controlSettings = { winrt::make<TerminalSettings>(_settings, current_guid.value(), *_bindings) };
const auto workingDirectory = focusedTab->GetActiveTerminalControl().WorkingDirectory();
const auto validWorkingDirectory = !workingDirectory.empty();
if (validWorkingDirectory)
{
controlSettings.StartingDirectory(workingDirectory);
}
realGuid = current_guid.value();
}
// TODO: GH#5047 - In the future, we should get the Profile of
@@ -1647,7 +1717,7 @@ namespace winrt::TerminalApp::implementation
realSplitType = focusedTab->PreCalculateAutoSplit(availableSpace);
}
const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, availableSpace);
const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, splitSize, availableSpace);
if (!canSplit)
{
return;
@@ -1660,7 +1730,7 @@ namespace winrt::TerminalApp::implementation
_UnZoomIfNeeded();
focusedTab->SplitPane(realSplitType, realGuid, newControl);
focusedTab->SplitPane(realSplitType, splitSize, realGuid, newControl);
}
CATCH_LOG();
}
@@ -1675,13 +1745,10 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_ResizePane(const ResizeDirection& direction)
{
if (auto index{ _GetFocusedTabIndex() })
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
_UnZoomIfNeeded();
terminalTab->ResizePane(direction);
}
_UnZoomIfNeeded();
terminalTab->ResizePane(direction);
}
}
@@ -1692,14 +1759,8 @@ namespace winrt::TerminalApp::implementation
// - scrollDirection: ScrollUp will move the viewport up, ScrollDown will move the viewport down
void TerminalPage::_ScrollPage(ScrollDirection scrollDirection)
{
auto indexOpt = _GetFocusedTabIndex();
// Do nothing if for some reason, there's no tab in focus. We don't want to crash.
if (!indexOpt)
{
return;
}
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt)))
// Do nothing if for some reason, there's no terminal tab in focus. We don't want to crash.
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
const auto control = _GetActiveControl();
const auto termHeight = control.GetViewHeight();
@@ -1710,13 +1771,10 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_ScrollToBufferEdge(ScrollDirection scrollDirection)
{
if (const auto indexOpt = _GetFocusedTabIndex())
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt)))
{
auto scrollDelta = _ComputeScrollDelta(scrollDirection, INT_MAX);
terminalTab->Scroll(scrollDelta);
}
auto scrollDelta = _ComputeScrollDelta(scrollDirection, INT_MAX);
terminalTab->Scroll(scrollDelta);
}
}
@@ -1826,12 +1884,9 @@ namespace winrt::TerminalApp::implementation
{
if (_settings && _settings.GlobalSettings().SnapToGridOnResize())
{
if (auto index{ _GetFocusedTabIndex() })
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
return terminalTab->CalcSnappedDimension(widthOrHeight, dimension);
}
return terminalTab->CalcSnappedDimension(widthOrHeight, dimension);
}
}
return dimension;
@@ -1927,8 +1982,9 @@ namespace winrt::TerminalApp::implementation
}
}
const bool hasNewLine = std::find(text.cbegin(), text.cend(), L'\n') != text.cend();
const bool warnMultiLine = hasNewLine && _settings.GlobalSettings().WarnAboutMultiLinePaste();
const auto isNewLineLambda = [](auto c) { return c == L'\n' || c == L'\r'; };
const auto hasNewLine = std::find_if(text.cbegin(), text.cend(), isNewLineLambda) != text.cend();
const auto warnMultiLine = hasNewLine && _settings.GlobalSettings().WarnAboutMultiLinePaste();
constexpr const std::size_t minimumSizeForWarning = 1024 * 5; // 5 KiB
const bool warnLargeText = text.size() > minimumSizeForWarning &&
@@ -1938,6 +1994,13 @@ namespace winrt::TerminalApp::implementation
{
co_await winrt::resume_foreground(Dispatcher());
// We have to initialize the dialog here to be able to change the text of the text block within it
FindName(L"MultiLinePasteDialog").try_as<WUX::Controls::ContentDialog>();
ClipboardText().Text(text);
// The vertical offset on the scrollbar does not reset automatically, so reset it manually
ClipboardContentScrollViewer().ScrollToVerticalOffset(0);
ContentDialogResult warningResult;
if (warnMultiLine)
{
@@ -1948,6 +2011,9 @@ namespace winrt::TerminalApp::implementation
warningResult = co_await _ShowLargePasteWarningDialog();
}
// Clear the clipboard text so it doesn't lie around in memory
ClipboardText().Text(L"");
if (warningResult != ContentDialogResult::Primary)
{
// user rejected the paste
@@ -2188,7 +2254,7 @@ namespace winrt::TerminalApp::implementation
// here, then the user won't be able to navigate the ATS any
// longer.
//
// When the tab swither is eventually dismissed, the focus will
// When the tab switcher is eventually dismissed, the focus will
// get tossed back to the focused terminal control, so we don't
// need to worry about focus getting lost.
if (CommandPalette().Visibility() != Visibility::Visible)
@@ -2197,8 +2263,13 @@ namespace winrt::TerminalApp::implementation
_UpdateMRUTab(index);
}
tab.TabViewItem().StartBringIntoView();
// Raise an event that our title changed
_titleChangeHandlers(*this, tab.Title());
if (_settings.GlobalSettings().ShowTitleInTitlebar())
{
_titleChangeHandlers(*this, tab.Title());
}
}
CATCH_LOG();
}
@@ -2212,7 +2283,7 @@ namespace winrt::TerminalApp::implementation
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabSelectionChanged(const IInspectable& sender, const WUX::Controls::SelectionChangedEventArgs& /*eventArgs*/)
{
if (!_rearranging)
if (!_rearranging && !_removing)
{
auto tabView = sender.as<MUX::Controls::TabView>();
auto selectedIndex = tabView.SelectedIndex();
@@ -2309,6 +2380,9 @@ namespace winrt::TerminalApp::implementation
{
settingsTab.UpdateSettings(_settings);
}
auto tabImpl{ winrt::get_self<TabBase>(tab) };
tabImpl->SetKeyMap(_settings.KeyMap());
}
auto weakThis{ get_weak() };
@@ -2353,7 +2427,7 @@ namespace winrt::TerminalApp::implementation
IVectorView<Profile> profiles,
IMapView<winrt::hstring, ColorScheme> schemes)
{
IVector<SettingsLoadWarnings> warnings;
IVector<SettingsLoadWarnings> warnings{ winrt::single_threaded_vector<SettingsLoadWarnings>() };
std::vector<ColorScheme> sortedSchemes;
sortedSchemes.reserve(schemes.Size());
@@ -2698,7 +2772,7 @@ namespace winrt::TerminalApp::implementation
// - an empty list if we failed to parse, otherwise a list of actions to execute.
std::vector<ActionAndArgs> TerminalPage::ConvertExecuteCommandlineToActions(const ExecuteCommandlineArgs& args)
{
::CommandlineArgs::AppCommandlineArgs appArgs;
::TerminalApp::AppCommandlineArgs appArgs;
if (appArgs.ParseArgs(args) == 0)
{
return appArgs.GetStartupActions();
@@ -2781,6 +2855,8 @@ namespace winrt::TerminalApp::implementation
sui.SetHostingWindow(reinterpret_cast<uint64_t>(*_hostingHwnd));
}
sui.PreviewKeyDown({ this, &TerminalPage::_SUIPreviewKeyDownHandler });
sui.OpenJson([weakThis{ get_weak() }](auto&& /*s*/, winrt::Microsoft::Terminal::Settings::Model::SettingsTarget e) {
if (auto page{ weakThis.get() })
{
@@ -2795,6 +2871,7 @@ namespace winrt::TerminalApp::implementation
_mruTabs.Append(*newTabImpl);
newTabImpl->SetDispatch(*_actionDispatch);
newTabImpl->SetKeyMap(_settings.KeyMap());
// Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command.
_UpdateTabIndices();
@@ -2837,7 +2914,7 @@ namespace winrt::TerminalApp::implementation
// Return Value:
// - If the tab is a TerminalTab, a com_ptr to the implementation type.
// If the tab is not a TerminalTab, nullptr
winrt::com_ptr<TerminalTab> TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab) const
winrt::com_ptr<TerminalTab> TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab)
{
if (auto terminalTab = tab.try_as<TerminalApp::TerminalTab>())
{
@@ -2997,6 +3074,32 @@ namespace winrt::TerminalApp::implementation
return text;
}
// Method Description:
// - Hides cursor if required
// Return Value:
// - <none>
void TerminalPage::_HidePointerCursorHandler(const IInspectable& /*sender*/, const IInspectable& /*eventArgs*/)
{
if (_shouldMouseVanish && !_isMouseHidden)
{
CoreWindow::GetForCurrentThread().PointerCursor(nullptr);
_isMouseHidden = true;
}
}
// Method Description:
// - Restores cursor if required
// Return Value:
// - <none>
void TerminalPage::_RestorePointerCursorHandler(const IInspectable& /*sender*/, const IInspectable& /*eventArgs*/)
{
if (_isMouseHidden)
{
CoreWindow::GetForCurrentThread().PointerCursor(_defaultPointerCursor);
_isMouseHidden = false;
}
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View File

@@ -10,7 +10,7 @@
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include "../CommandlineArgs/AppCommandlineArgs.h"
#include "AppCommandlineArgs.h"
static constexpr uint32_t DefaultRowsToScroll{ 3 };
static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
@@ -112,8 +112,8 @@ namespace winrt::TerminalApp::implementation
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _tabs;
Windows::Foundation::Collections::IVector<TerminalApp::TabBase> _mruTabs;
winrt::com_ptr<TerminalTab> _GetTerminalTabImpl(const TerminalApp::TabBase& tab) const;
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _mruTabs;
static winrt::com_ptr<TerminalTab> _GetTerminalTabImpl(const TerminalApp::TabBase& tab);
void _UpdateTabIndices();
@@ -126,6 +126,7 @@ namespace winrt::TerminalApp::implementation
bool _rearranging;
std::optional<int> _rearrangeFrom;
std::optional<int> _rearrangeTo;
bool _removing{ false };
uint32_t _systemRowsToScroll{ DefaultRowsToScroll };
@@ -157,6 +158,8 @@ namespace winrt::TerminalApp::implementation
void _AboutButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _SUIPreviewKeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) noexcept;
void _RegisterActionCallbacks();
@@ -182,7 +185,9 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
TerminalApp::TabBase _GetFocusedTab();
TerminalApp::TabBase _GetFocusedTab() const noexcept;
winrt::com_ptr<TerminalTab> _GetFocusedTabImpl() const noexcept;
winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex);
void _CloseFocusedTab();
void _CloseFocusedPane();
@@ -193,8 +198,13 @@ namespace winrt::TerminalApp::implementation
// Todo: add more event implementations here
// MSFT:20641986: Add keybindings for New Window
void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference<uint32_t>& rowsToScroll);
void _SplitPane(const Microsoft::Terminal::Settings::Model::SplitState splitType, const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual, const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
void _SplitPane(const Microsoft::Terminal::Settings::Model::SplitState splitType,
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
const float splitSize = 0.5f,
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void _ScrollPage(ScrollDirection scrollDirection);
void _ScrollToBufferEdge(ScrollDirection scrollDirection);
void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::TerminalControl::KeyChord& keyChord);
@@ -245,16 +255,19 @@ namespace winrt::TerminalApp::implementation
void _OpenSettingsUI();
void _MakeSwitchToTabCommand(const TerminalApp::TabBase& tab, const uint32_t index);
static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll);
static uint32_t _ReadSystemRowsToScroll();
void _UpdateTabSwitcherCommands(const bool mru);
void _UpdateMRUTab(const uint32_t index);
void _TryMoveTab(const uint32_t currentTabIndex, const int32_t suggestedNewTabIndex);
bool _shouldMouseVanish{ false };
bool _isMouseHidden{ false };
Windows::UI::Core::CoreCursor _defaultPointerCursor{ nullptr };
void _HidePointerCursorHandler(const IInspectable& sender, const IInspectable& eventArgs);
void _RestorePointerCursorHandler(const IInspectable& sender, const IInspectable& eventArgs);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
void _HandleOpenNewTabDropdown(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);

View File

@@ -58,6 +58,26 @@ the MIT License. See LICENSE in the project root for license information. -->
x:Name="MultiLinePasteDialog"
x:Uid="MultiLinePasteDialog"
DefaultButton="Primary">
<StackPanel>
<TextBlock
x:Uid="MultiLineWarningText"
TextWrapping="Wrap">
</TextBlock>
<TextBlock
x:Uid="ClipboardTextHeader"
Margin="0,16,0,0">
</TextBlock>
<ScrollViewer
Margin="0,8,0,0"
x:Name="ClipboardContentScrollViewer"
MaxHeight="100">
<TextBlock
x:Name="ClipboardText"
TextWrapping="Wrap"
FontFamily="Cascadia Mono">
</TextBlock>
</ScrollViewer>
</StackPanel>
</ContentDialog>
<ContentDialog
@@ -95,6 +115,7 @@ the MIT License. See LICENSE in the project root for license information. -->
x:Name="CommandPalette"
Grid.Row="1"
Visibility="Collapsed"
PreviewKeyDown ="_KeyDownHandler"
VerticalAlignment="Stretch" />
<mux:InfoBar x:Name="KeyboardWarningInfoBar"

View File

@@ -51,10 +51,28 @@ namespace winrt::TerminalApp::implementation
}
});
_UpdateHeaderControlMaxWidth();
// Use our header control as the TabViewItem's header
TabViewItem().Header(_headerControl);
}
// Method Description:
// - Called when the timer for the bell indicator in the tab header fires
// - Removes the bell indicator from the tab header
// Arguments:
// - sender, e: not used
void TerminalTab::_BellIndicatorTimerTick(Windows::Foundation::IInspectable const& /*sender*/, Windows::Foundation::IInspectable const& /*e*/)
{
ShowBellIndicator(false);
// Just do a sanity check that the timer still exists before we stop it
if (_bellIndicatorTimer.has_value())
{
_bellIndicatorTimer->Stop();
_bellIndicatorTimer = std::nullopt;
}
}
// Method Description:
// - Initializes a TabViewItem for this Tab instance.
// Arguments:
@@ -76,6 +94,26 @@ namespace winrt::TerminalApp::implementation
_RecalculateAndApplyTabColor();
}
winrt::fire_and_forget TerminalTab::_UpdateHeaderControlMaxWidth()
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
if (auto tab{ weakThis.get() })
{
const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() };
if (settings.GlobalSettings().TabWidthMode() == winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::SizeToContent)
{
tab->_headerControl.RenamerMaxWidth(HeaderRenameBoxWidthTitleLength);
}
else
{
tab->_headerControl.RenamerMaxWidth(HeaderRenameBoxWidthDefault);
}
}
}
// Method Description:
// - Returns nullptr if no children of this tab were the last control to be
// focused, or the TermControl that _was_ the last control to be focused (if
@@ -123,6 +161,11 @@ namespace winrt::TerminalApp::implementation
lastFocusedControl.Focus(_focusState);
lastFocusedControl.TaskbarProgressChanged();
}
// When we gain focus, remove the bell indicator if it is active
if (_headerControl.BellIndicator())
{
ShowBellIndicator(false);
}
}
}
@@ -163,6 +206,9 @@ namespace winrt::TerminalApp::implementation
void TerminalTab::UpdateSettings(const winrt::TerminalApp::TerminalSettings& settings, const GUID& profile)
{
_rootPane->UpdateSettings(settings, profile);
// The tabWidthMode may have changed, update the header control accordingly
_UpdateHeaderControlMaxWidth();
}
// Method Description:
@@ -213,17 +259,57 @@ namespace winrt::TerminalApp::implementation
if (auto tab{ weakThis.get() })
{
if (hide)
if (tab->_iconHidden != hide)
{
Icon({});
TabViewItem().IconSource(IconPathConverter::IconSourceMUX({}));
tab->_iconHidden = true;
if (hide)
{
Icon({});
TabViewItem().IconSource(IconPathConverter::IconSourceMUX({}));
}
else
{
Icon(_lastIconPath);
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
}
tab->_iconHidden = hide;
}
else
}
}
// Method Description:
// - Hide or show the bell indicator in the tab header
// Arguments:
// - show: if true, we show the indicator; if false, we hide the indicator
winrt::fire_and_forget TerminalTab::ShowBellIndicator(const bool show)
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
if (auto tab{ weakThis.get() })
{
tab->_headerControl.BellIndicator(show);
}
}
// Method Description:
// - Activates the timer for the bell indicator in the tab
// - Called if a bell raised when the tab already has focus
winrt::fire_and_forget TerminalTab::ActivateBellIndicatorTimer()
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
if (auto tab{ weakThis.get() })
{
if (!tab->_bellIndicatorTimer.has_value())
{
Icon(_lastIconPath);
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
tab->_iconHidden = false;
DispatcherTimer bellIndicatorTimer;
bellIndicatorTimer.Interval(std::chrono::milliseconds(2000));
bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick });
bellIndicatorTimer.Start();
tab->_bellIndicatorTimer.emplace(std::move(bellIndicatorTimer));
}
}
}
@@ -265,6 +351,8 @@ namespace winrt::TerminalApp::implementation
// Update the control to reflect the changed title
_headerControl.Title(activeTitle);
Automation::AutomationProperties::SetName(tab->TabViewItem(), activeTitle);
_UpdateToolTip();
}
}
@@ -283,18 +371,7 @@ namespace winrt::TerminalApp::implementation
co_await winrt::resume_foreground(control.Dispatcher());
const auto currentOffset = control.GetScrollOffset();
control.ScrollViewport(currentOffset + delta);
}
// Method Description:
// - Determines whether the focused pane has sufficient space to be split.
// Arguments:
// - splitType: The type of split we want to create.
// Return Value:
// - True if the focused pane can be split. False otherwise.
bool TerminalTab::CanSplitPane(SplitState splitType)
{
return _activePane->CanSplit(splitType);
control.ScrollViewport(::base::ClampAdd(currentOffset, delta));
}
// Method Description:
@@ -306,14 +383,27 @@ namespace winrt::TerminalApp::implementation
// - control: A TermControl to use in the new pane.
// Return Value:
// - <none>
void TerminalTab::SplitPane(SplitState splitType, const GUID& profile, TermControl& control)
void TerminalTab::SplitPane(SplitState splitType,
const float splitSize,
const GUID& profile,
TermControl& control)
{
// Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID
const auto activePaneId = _activePane->Id();
auto [first, second] = _activePane->Split(splitType, profile, control);
first->Id(activePaneId);
second->Id(_nextPaneId);
++_nextPaneId;
auto [first, second] = _activePane->Split(splitType, splitSize, profile, control);
if (activePaneId)
{
first->Id(activePaneId.value());
second->Id(_nextPaneId);
++_nextPaneId;
}
else
{
first->Id(_nextPaneId);
++_nextPaneId;
second->Id(_nextPaneId);
++_nextPaneId;
}
_activePane = first;
_AttachEventHandlersToControl(control);
@@ -531,16 +621,18 @@ namespace winrt::TerminalApp::implementation
// We need to move the pane to the top of our mru list
// If its already somewhere in the list, remove it first
const auto paneId = pane->Id();
for (auto i = _mruPanes.begin(); i != _mruPanes.end(); ++i)
if (const auto paneId = pane->Id())
{
if (*i == paneId)
for (auto i = _mruPanes.begin(); i != _mruPanes.end(); ++i)
{
_mruPanes.erase(i);
break;
if (*i == paneId.value())
{
_mruPanes.erase(i);
break;
}
}
_mruPanes.insert(_mruPanes.begin(), paneId.value());
}
_mruPanes.insert(_mruPanes.begin(), paneId);
// Raise our own ActivePaneChanged event.
_ActivePaneChangedHandlers();
}
@@ -563,10 +655,30 @@ namespace winrt::TerminalApp::implementation
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() };
if (tab && sender != tab->_activePane)
if (tab)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
if (sender != tab->_activePane)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
}
tab->_focusState = WUX::FocusState::Programmatic;
// This tab has gained focus, remove the bell indicator if it is active
if (tab->_headerControl.BellIndicator())
{
tab->ShowBellIndicator(false);
}
}
});
pane->LostFocus([weakThis](std::shared_ptr<Pane> /*sender*/) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() };
if (tab)
{
// update this tab's focus state
tab->_focusState = WUX::FocusState::Unfocused;
}
});
@@ -597,13 +709,27 @@ namespace winrt::TerminalApp::implementation
}
});
// Add a PaneRaiseVisualBell event handler to the Pane. When the pane emits this event,
// we need to bubble it all the way to app host. In this part of the chain we bubble it
// from the hosting tab to the page.
pane->PaneRaiseVisualBell([weakThis](auto&& /*s*/) {
// Add a PaneRaiseBell event handler to the Pane
pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) {
if (auto tab{ weakThis.get() })
{
tab->_TabRaiseVisualBellHandlers();
if (visual)
{
// If visual is set, we need to bubble this event all the way to app host to flash the taskbar
// In this part of the chain we bubble it from the hosting tab to the page
tab->_TabRaiseVisualBellHandlers();
}
// Show the bell indicator in the tab header
tab->ShowBellIndicator(true);
// If this tab is focused, activate the bell indicator timer, which will
// remove the bell indicator once it fires
// (otherwise, the indicator is removed when the tab gets focus)
if (tab->_focusState != WUX::FocusState::Unfocused)
{
tab->ActivateBellIndicatorTimer();
}
}
});
}
@@ -688,7 +814,6 @@ namespace winrt::TerminalApp::implementation
newTabFlyout.Items().Append(chooseColorMenuItem);
newTabFlyout.Items().Append(renameTabMenuItem);
newTabFlyout.Items().Append(menuSeparator);
newTabFlyout.Items().Append(_CreateCloseSubMenu());
newTabFlyout.Items().Append(closeTabMenuItem);
TabViewItem().ContextFlyout(newTabFlyout);
}
@@ -936,9 +1061,11 @@ namespace winrt::TerminalApp::implementation
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
}
bool TerminalTab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const
bool TerminalTab::PreCalculateCanSplit(SplitState splitType,
const float splitSize,
winrt::Windows::Foundation::Size availableSpace) const
{
return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false);
return _rootPane->PreCalculateCanSplit(_activePane, splitType, splitSize, availableSpace).value_or(false);
}
// Method Description:
@@ -984,6 +1111,27 @@ namespace winrt::TerminalApp::implementation
return _zoomedPane != nullptr;
}
// Method Description:
// - Creates a text for the title run in the tool tip by returning tab title
// or <profile name>: <tab title> in the case the profile name differs from the title
// Arguments:
// - <none>
// Return Value:
// - The value to populate in the title run of the tool tip
winrt::hstring TerminalTab::_CreateToolTipTitle()
{
if (const auto& control{ GetActiveTerminalControl() })
{
const auto profileName{ control.Settings().ProfileName() };
if (profileName != Title())
{
return fmt::format(L"{}: {}", profileName, Title()).data();
}
}
return Title();
}
DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>);

View File

@@ -7,6 +7,9 @@
#include "TabBase.h"
#include "TerminalTab.g.h"
static constexpr double HeaderRenameBoxWidthDefault{ 165 };
static constexpr double HeaderRenameBoxWidthTitleLength{ std::numeric_limits<double>::infinity() };
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
@@ -30,15 +33,22 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget Scroll(const int delta);
bool CanSplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
const GUID& profile,
winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
winrt::fire_and_forget HideIcon(const bool hide);
winrt::fire_and_forget ShowBellIndicator(const bool show);
winrt::fire_and_forget ActivateBellIndicatorTimer();
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
winrt::Microsoft::Terminal::Settings::Model::SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
bool PreCalculateCanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const;
bool PreCalculateCanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
winrt::Windows::Foundation::Size availableSpace) const;
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
@@ -71,6 +81,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
FORWARDED_TYPED_EVENT(TabRenamerDeactivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, (&_headerControl), RenameEnded);
private:
std::shared_ptr<Pane> _rootPane{ nullptr };
@@ -96,9 +107,15 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer;
void _BellIndicatorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
void _MakeTabViewItem();
winrt::fire_and_forget _UpdateHeaderControlMaxWidth();
void _CreateContextMenu() override;
virtual winrt::hstring _CreateToolTipTitle() override;
void _RefreshVisualState();

View File

@@ -2,5 +2,5 @@
<packages>
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.2" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.201017.1" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
</packages>

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