Compare commits

...

159 Commits

Author SHA1 Message Date
Dustin L. Howett
595721658a Migrate spelling-0.0.21 changes from main 2021-09-14 05:47:19 -05:00
Mike Griese
306ad30753 minor nits 2021-09-14 05:47:19 -05:00
Mike Griese
00c7647594 🤦 2021-09-14 05:45:45 -05:00
Mike Griese
97b0f06504 This is everything from dev/migrie/f/non-terminal-content-elevation-warning for specifically ElevatedState 2021-09-14 05:44:25 -05:00
Mike Griese
c2fcdcbe10 THis fixes #7754 2021-09-14 05:23:15 -05:00
Dustin L. Howett
3d7480e9b7 Upgrade to C++/WinRT 2.0.210825.3 (#11188)
This pull request moves us to Microsoft.Windows.CppWinRT 2.0.210825.3.

Notable improvements from 2.0.210309.3:
* Restored Windows 7 functionality
* C++20 ranges support
* `capture` now works with a raw pointer
* `hstring::starts_with` and `hstring::ends_with` (C++20)

Unit/Functional Tests:
Summary: Total=7728, Passed=7571, Failed=10, Blocked=0, Not Run=0, Skipped=147

Local Tests:
Summary: Total=163, Passed=158, Failed=5, Blocked=0, Not Run=0, Skipped=0

The above failures are (1) in UIA tests for conhost/WT (which do not work here) or
(2) in already known-broken local tests.
2021-09-10 21:33:13 +00:00
PankajBhojwani
97722d3efe Add an openSystemMenu keybinding (#11086)
## Summary of the Pull Request
Basically undoes #10988  in favour of implementing it as described in #11018 

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

## Validation Steps Performed

- alt+space opens the system menu by default
- when alt+space is bound, the keys do not get send to terminal
- right-click on the tab bar didn't break (still opens system menu at the location of the cursor)
2021-09-10 18:25:43 +00:00
Don-Vito
a900ababdc Teach info bars to be dismissed permanently (#11139)
## Summary of the Pull Request
* Introduces info bar shown upon session failure, 
that guides the user how to configure termination behavior
  * Allows this info bar to be dismissed permanently (choice stored in state) 
* Allows "keyboard service" info bar to be dismissed permanently

## PR Checklist
* [x] Closes #10798, #8699
* [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
UI:
* Introduce an additional info bar for "close on exit" configuration tip
  * Stack this bar after "keyboard service" bar
* Add "Don't show again" button to both bars

Dismiss Permanently:
* Introduce a set of "dismissed messages" to the Application State
* Add verification the message is not dismissed before showing an info bar
* "Don't show  again" persists the choice under "dismissed messages"

Wiring the Info Bar:
* Register `TerminalPage` on `TermControl`'s `ConnectionStateChanged` event
* Once event is triggered check whether the state is failure
* If so and the message was not dismissed permanently, show the info bar
2021-09-10 17:16:41 +00:00
gabrielconl
54ed295588 Search box visual tweaks (#11105)
Made some changes to the search box:
* Adjusted spacing inside the box
* Detached the search box from the titlebar (as explained [here](https://github.com/microsoft/terminal/issues/1375#issuecomment-856667292))
* The search box is now 8px further to the left, in case the scrollbar is always enabled
* Made some controls use default properties, so that they'll adjust nicely to the 2.6 styles

Other: the search box and command palette now use OverlayCornerRadius

Before/After:
![image](https://user-images.githubusercontent.com/84711285/131888377-513b9de4-a653-4086-9a67-8718c64dc75b.png)
2021-09-09 18:00:46 +00:00
Schuyler Rosefield
bee6fb4368 Add the ability to quit all terminal instances (#11143)
Add the ability to quit all terminal instances. Doing this separately from the window layout saving ones to lessen the number of 1k+ line monsters I make y'all review.

## References
#11083 

## PR Checklist
* [x] Closes #11081
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [x] Schema updated.

## Detailed Description of the Pull Request / Additional comments
- Warn the user before they do so to give a chance to cancel
- Percolate a QuitAll event up to the monarch who then directs each peasant to clsoe.
- Leave a window-layout-saving-sized hole to add that feature on top

## Validation Steps Performed
- quit with one window (from the monarch)
- quit from the monarch with multiple windows
- quit from a peasant
- cancel the quit dialog

![image](https://user-images.githubusercontent.com/6185249/132105775-3310f614-ce55-4454-9718-ef5c0d39fbd2.png)
2021-09-09 14:03:03 +00:00
James Holderness
6140fd9ab8 Add basic support for the DECRQSS settings query (#11152)
This PR adds support for the `DECRQSS` (Request Selection or Setting)
escape sequence, which is a standard VT query for reporting the state of
various control functions. This initial implementation only supports
queries for the `DECSTBM` margins, and the `SGR` graphic rendition
attributes.

This can be useful for certain forms of capability detection (#1040). As
one example in particular, it can serve as an alternative to the
`COLORTERM` environment variable for detecting truecolor support
(#11057). 

Of the settings that can be queried by `DECRQSS`, the only other one
that we could be supporting at the moment is `DECSCUSR` (Cursor Style).
However, that would require passing the query through to the conpty
client, which is a lot more complicated, so I thought it best to leave
for a future PR.

For now this gets the basic framework in place, so we are at least
responding to queries, and even just supporting the `SGR` attributes
query is useful in itself.

Validation
----------
I've added a unit test verifying the reports for the `DECSTBM` and `SGR`
settings with a range of different parameters. I've also tested the
`DECSTBM` and `SGR` reports manually in _Vttest_, under menu 11.2.5.3.6
(Status-String Reports).
2021-09-08 23:26:44 +00:00
Schuyler Rosefield
13e9546bab Persist window layout on window close (#10972)
This commit adds initial support for saving window layout on application
close.

Done:
- Add user setting for if tabs should be maintained.
- Added events to track the number of open windows for the monarch, and
  then save if you are the last window closing.
- Saves layout when the user explicitly hits the "Close Window" button.
- If the user manually closed all of their tabs (through the tab x
  button or through closing all panes on the tab) then remove any saved
  state.
- Saves in the ApplicationState file a list of actions the terminal can
  perform to restore its layout and the window size/position
  information.
- This saves an action to focus the correct pane, but this won't
  actually work without #10978. Note that if you have a pane zoomed, it
  does still zoom the correct pane, but when you unzoom it will have a
  different pane selected.

Todo:
- multiple windows? Right now it can only handle loading/saving one
  window.
   - PR #11083 will save multiple windows.
- This also sometimes runs into the existing bug where multiple tabs
  appear to be focused on opening.

Next Steps:
- The business logic of when the save is triggered can be adjusted as
  necessary.
- Right now I am taking the pragmatic approach and just saving the state
  as an array of objects, but only ever populate it with 1, that way
  saving multiple windows in the future could be added without breaking
  schema compatibility. Selfishly I'm hoping that handling multiple
  windows could be spun off into another pr/feature for now.
- One possible thing that can maybe be done is that the commandline can
  be augmented with a "--saved ##" attribute that would load from the
  nth saved state if it exists. e.g. if there are 3 saved windows, on
  first load it can spawn three wt --saved {0,1,2} that would reopen the
  windows? This way there also exists a way to load a copy of a previous
  window (if it is in the saved state).
- Is the application state something that is planned to be public/user
  editable? In theory the user could since it is just json, but I don't
  know what it buys them over just modifying their settings and
  startupActions.

Validation Steps Performed:
- The happy path: open terminal -> set setting to true -> close terminal
  -> reopen and see tabs. Tested with powershell/cmd/wsl windows.
- That closing all panes/tabs on their own will remove the saved
  session.
- Open multiple windows, close windows and confirm that the last window
  closed saves its state.

The generated file stores a sequence of actions that will be executed to
restore the terminal to its saved form.

References #8324
This is also one of the items on microsoft/terminal#5000
Closes #766
2021-09-08 22:44:53 +00:00
Schuyler Rosefield
0a48836e83 Fix tab movement while running multiple actions (#11144)
## Summary of the Pull Request
Make sure to always synchronously set the selected tab. This way when changing tabs while running multiple actions further calls to _GetFocusedTab will return the correct one.

**Edit** #11146  discovered while trying to test this so while I fixed the case I wanted, things seem to be broken generally so it is hard for me to test if I broke anything else.

## References

## PR Checklist
* [x] Closes #11107 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA

## Validation Steps Performed
Ran the command specified in the issue and confirmed that the correct tab was focused and that the correct pane was zoomed.
2021-09-07 13:34:48 -07:00
Ian O'Neill
43c76ee240 Disable autocorrect for command, path and find text inputs (#11137)
## Summary of the Pull Request
Disables autocorrect for command, path and find text inputs. Does not disable it for profile names, tab titles or colour scheme names.

## PR Checklist
* [x] Closes #11133
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA

## Validation Steps Performed
Manually typed `bash -i -l` into the profile command text input and found it no longer auto-capitalised the I.
2021-09-07 10:53:53 -07:00
Leon Liang
424414ec97 Provide the focused tab title in the Tray Icon's context menu (#11043)
This PR adds a bit more information to each item in the Tray Icon's  window selection submenu. 
Currently it only shows the window ID and window name if given one. 
Now each item will instead show`{Window ID} : {Active Tab Title} [{Window Name}]`

![image](https://user-images.githubusercontent.com/57155886/130883675-7a76e674-2429-4b26-b869-2455a9e4b4f6.png)
2021-09-03 18:32:23 +00:00
Kayla Cinnamon
4f6f3b98b8 Add useAcrylicInTabRow to JSON schema (#11117)
`useAcrylicInTabRow` was missing from the JSON schema, so I added it in.

## References
#10864 

## PR Checklist
* [x] Closes #11087 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Schema updated.
2021-09-02 11:02:44 -07:00
Mike Griese
6268a4779c Implement and action for manually clearing the Terminal (and conpty) buffer (#10906)
## Summary of the Pull Request

![clear-buffer-000](https://user-images.githubusercontent.com/18356694/127570078-90c6089e-0430-4dfc-bcd4-a0cde20c9167.gif)

This adds a new action, `clearBuffer`. It accepts 3 values for the `clear` type:
* `"clear": "screen"`: Clear the terminal viewport content. Leaves the scrollback untouched. Moves the cursor row to the top of the viewport (unmodified).
* `"clear": "scrollback"`: Clear the scrollback. Leaves the viewport untouched.
* `"clear": "all"`: (**default**) Clear the scrollback and the visible viewport. Moves the cursor row to the top of the viewport (unmodified).

"Clear Buffer" has also been added to `defaults.json`.

## References
* From microsoft/vscode#75141 originally

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

## Detailed Description of the Pull Request / Additional comments

This is a bit tricky, because we need to plumb it all the way through conpty to clear the buffer. If we don't, then conpty will immediately just redraw the screen. So this sends a signal to the attached conpty, and then waits for conpty to draw the updated, cleared, screen back to us.

## Validation Steps Performed
* works for each of the three clear types as expected
* tests pass.
* works even with `ping -t 8.8.8.8` as you'd hope.
2021-09-02 14:59:42 +00:00
Schuyler Rosefield
13bc71de3c Maintain zoom when moving focus (#11046)
<!-- 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
Make it so you can navigate pane focus without unzooming.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #7215
* [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
- Slight refactor to bring the MRU pane logic into the `NavigateDirection` function
- The actual zoom behavior was not a problem, the only issue is that because most of the panes weren't in the UI tree I had to disable using the actual sizes. There is nothing wrong with that, since the synthetic sizing is required anyways, but I'm curious what other peoples' thoughts are.

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

![output](https://user-images.githubusercontent.com/6185249/130901911-91676da2-db40-412d-b726-61a3f559ae17.gif)
2021-09-02 14:36:17 +00:00
gabrielconl
a0670cb6b3 Make TabView padding equal (#11115)
Doing #10242 again.

The space around the tabs was made equal in windowed mode.
For maximized mode, I made the titlebar be 33px tall, to compensate for #10746.

![padding](https://user-images.githubusercontent.com/84711285/131723737-d63b015c-2134-465a-a15b-6b44538b95c5.png)
2021-09-02 14:34:03 +00:00
Don-Vito
7908164f9d Teach Command Palette to filter out duplicate command lines (#11116)
Closes #11093
2021-09-02 03:03:52 +00:00
Elisha Hollander
8ffea2c177 Remove time and sys (#11100)
## Summary of the Pull Request

Remove those imports as they are unnecessary, _template.py_ contains these too but I guess it's fine since it's a template after all
2021-09-01 21:30:39 +00:00
Mahdi Hosseini
e0853ae4cc Update terminal-v2-roadmap.md links (#11103)
Update terminal-v2-roadmap to include recent blog posts
2021-09-01 14:26:23 -07:00
Don-Vito
c089ae0c57 Allow exporting terminal buffer into file via tab context menu (#11062)
## Summary of the Pull Request
**Naive implementation** of exporting the text buffer of the current pane
into a text file triggered from the tab context menu.

**Disclaimer: this is not an export of the command  history,** 
but rather just a text buffer dumped into a file when asked explicitly.

## References
Should provide partial solution for #642.

## Detailed Description of the Pull Request / Additional comments
The logic is following:
* Open a file save picker
  * The location is Downloads folder (should be always accessible)
  * The suggest name of the file equals to the pane's title
  * The allowed file formats list contains .txt only
* If no file selected stop
* Lock terminal
* Read all lines till the cursor
* Format each line by removing trailing white-spaces and adding CRLF if not wrapped
* Asynchronously write to selected file
* Show confirmation

As the action is relatively fast didn't add a progress bar or any other UX.
As the buffer is relatively small, holding it entirely in the memory rather than
writing line by line to disk.
2021-08-31 19:36:43 +00:00
Schuyler Rosefield
8d81497eb7 Add action to run multiple actions. (#11045)
<!-- 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 a new action that can contain multiple other actions.

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

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

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Creates a shortcut action that allows a list of actions to be specified as arguments. Steals a bunch of the serialization code from my other pr. Overall, because I had the serialization code written already, this was remarkably easy.

I can't think of any combined action to be added to the defaults, so I think this is just a thing for the documentation unless someone else has a good example. I know there are lot of times when the recommended workaround is "make an action with commandline wt.exe ..." and this could be a good replacement for that, but that is all personalized.

I didn't add this to the command line parsing, since the command line is already a way to run multiple actions.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Created a new command, confirmed that "Move right->down" showed up in the command palette, and that running it did the correct behavior (moving right one pane, then down one pane).
```
      {
        "command": {
          "action": "multipleActions",
          "name": "Move right->down",
          "actions": [
            {"action":  "moveFocus", "direction": "right" },
            {"action":  "moveFocus", "direction": "down" },
          ]
        }
      }
```
2021-08-31 19:35:51 +00:00
Mike Griese
717ea85c9f Fix a crash when there aren't any recentCommands yet (#11082)
The first time you open commandline mode, `recentCommands` doesn't exist yet. However, we immediately try to read the `Size()` in a couple places. This'll A/V and we'll crash 😨 

The fix is easy - don't try and read the size of the non-existent `recentCommands`

Found this while playing with #11069
Regressed in #11030 
Didn't bother filing an issue for it when I have the fix in hand
2021-08-31 11:07:30 +00:00
Leon Liang
efea1e5bad Add Tray Icon settings to the SettingsUI (#11070)
Adds toggle buttons to the settings UI for `minimizeToTray` and `alwaysShowTrayIcon` that I mistakenly left out.
2021-08-31 01:39:03 +00:00
Don-Vito
871b8de74f Teach command palette to fill in selected commandline upon right arrow (#11069)
Closes #11049
2021-08-30 18:35:43 +00:00
PankajBhojwani
e4c5e8bd2a doc: add font features/axes to the schema (#11066)
Add entries to the schema for font features and axes

* [x] Closes #11058
2021-08-27 15:42:15 -05:00
SaintMalik
1acfef60f6 Fix typos found in terminal/oss (#11048) 2021-08-26 16:40:26 -05:00
Dustin L. Howett
de379cd043 Update Cascadia Code to 2108.26 (#11061)
This update fixes some minor ligature issues, font selection issues and
a problem with the Hebrew letter Vav when combined with Holam.

See microsoft/cascadia-code#538 for more details.
2021-08-26 14:54:32 -05:00
Don-Vito
7112f4e081 Teach CommandPalette to persist recent command lines (#11030)
Closes #11026
2021-08-26 19:04:35 +00:00
Mike Griese
7423734a48 Update pattern locations again after scrolling (#11059)
This is on me. When I got rid of the `_updatePatternLocations` `ThrottledFunc` in the `TermControl`, I didn't add a matching call to `_updatePatternLocations->Run()` in this method.

In #9820, in `TermControl::_ScrollPositionChanged`, there was still a call to `_updatePatternLocations->Run();`. (TermControl.cpp:1655 on the right) https://github.com/microsoft/terminal/pull/9820/files#diff-c10bb023995e88dac6c1d786129284c454c2df739ea547ce462129dc86dc2697R1654

#10051 didn't change this

In #10187 I moved the `_updatePatternLocations` throttled func from termcontrol to controlcore. Places it existed before:
* [x] `TermControl::_coreReceivedOutput`: already matched by ControlCore::_connectionOutputHandler
* [x] `TermControl::_ScrollbarChangeHandler` -> added in c20eb9d
* [x] `TermControl::_ScrollPositionChanged` -> `ControlCore::_terminalScrollPositionChanged`

## Validation Steps Performed
Print a URL, scroll the wheel: it still works.

Closes #11055
2021-08-26 18:57:50 +00:00
Schuyler Rosefield
07dc0601f9 Add first pane movement for MoveFocus/SwapPane. (#11044)
This commit adds the ability to target the first pane in the tree,
always.

I wasn't able to find an existing issue for this, it is just a personal
feature for me. I won't be heartbroken if it does not get merged.

As motivation, I frequently have setups where the thing I am primarily
working on is a large pane on the left and everything else is in smaller
panes positioned elsewhere. I like to have one hotkey where I can go to
any pane and then make it the "primary" pane if I am changing what I am
working on or need to focus on another set of code/documentation/etc.

## Validation Steps Performed
Confirmed that the move focus and swap pane variants both affect the
correct pane.
2021-08-26 17:58:56 +00:00
Michael Niksa
6f42367ab8 fix version specification because nuget only likes dashes. (#11060) 2021-08-26 10:58:02 -07:00
Dustin Howett
d2c72e5c25 version: bump to 1.12 on main 2021-08-26 11:35:27 -05:00
Dustin L. Howett
92437d718f build: propagate PGOBuildMode into final MSBuild command (#11054) 2021-08-26 11:30:49 -05:00
Michael Niksa
817f598e20 Move PGO Helix pools (#11028)
Moves PGO runs to supported Helix pools. We need to match Microsoft-UI-XAML on which Helix pools we used for each type of activities.

## PR Checklist
* [x] Closes #10850
* [x] I work here
* [x] If it builds, it sits.

## Validation Steps Performed
* [x] Run PGO build against this branch
2021-08-25 22:58:06 +00:00
Schuyler Rosefield
2c5a35f1be Make sure we keep event handlers on the control when detaching a pane (#11039)
When moving a pane to a new tab previously we removed the event handlers
on it as if we were closing it, but we are just moving it so we need to
keep them.

I tried really hard to make sure all of the events were hooked up
correctly, but I guess I missed these originally since they are normally
created in the Pane constructor.

Closes #11035

## Validation Steps Performed
created panes, moved them to new tabs, confirmed that they close and
ding appropriately.
2021-08-25 22:49:26 +00:00
Dustin L. Howett
ea58e4036b Use the "base" profile for incoming handoff and new commands (#11022)
This pull request introduces our first use of the "base" profile as an
actual profile. Incoming commandlines from `wt foo` *and* default
terminal handoffs will be hosted in the base profile.

**THIS IS A BREAKING CHANGE** for user behavior.

The original behavior where commandlines were hosted in the "default"
profile (in most cases, Windows PowerShell) led to user confusion: "why
does cmd use my powershell icon?" and "why does the title say
PowerShell?". Making this change unifies the user experience so that we
can land commandline detection in #10952.

Users who want the original behavior can get it back for commandline
invocation by specifying a profile using the `-p` argument, as in `wt -p
PowerShell -- cmd`.

As a temporary stopgap, users who attempt to duplicate the base profile
will get their specified default profile until we land #5047.

This feature is hidden behind the same feature flag that controls the
visibility of base/"Defaults" in the settings UI.

Fixes #10669
Related to #6776
2021-08-25 22:41:42 +00:00
Schuyler Rosefield
ee8800c739 Only attempt to focus if there is a control to focus (#11040)
Only focus if there is a control to focus (which may be null if e.g. the focused tab is being destroyed)

Closes #11037 

## Additional comments
I tried to remove the _activePane = nullptr in `TerminalTab::DetachPane` but that actually completely broke being able to focus the control at all making the tab completely unusable. Focus does seem to transfer just fine here with this change.

## Validation Steps Performed
Used the command execution to move panes to and from existing panes, including new tabs and destroying tabs.
2021-08-25 21:50:25 +00:00
Mike Griese
f7b0f7444a Spec for Elevation QOL improvements (#8455)
### ⇒ [doc link](https://github.com/microsoft/terminal/blob/dev/migrie/s/1032-elevation-qol/doc/specs/%235000%20-%20Process%20Model%202.0/%231032%20-%20Elevation%20Quality%20of%20Life%20Improvements.md) ⇐


## Summary of the Pull Request

Despite my best efforts to mix elevation levels in a single Terminal window, it seems that there's no way to do that safely. With the dream of mixed elevation dead, this spec outlines a number of quality-of-life improvements we can make to the Terminal today. These should make using the terminal in elevated scenarios better, since we can't have M/E.

### Abstract

> For a long time, we've been researching adding support to the Windows Terminal
> for running both unelevated and elevated (admin) tabs side-by-side, in the same
> window. However, after much research, we've determined that there isn't a safe
> way to do this without opening the Terminal up as a potential
> escalation-of-privilege vector.
> 
> Instead, we'll be adding a number of features to the Terminal to improve the
> user experience of working in elevated scenarios. These improvements include:
> 
> * A visible indicator that the Terminal window is elevated ([#1939])
> * Configuring the Terminal to always run elevated ([#632])
> * Configuring a specific profile to always open elevated ([#632])
> * Allowing new tabs, panes to be opened elevated directly from an unelevated
>   window
> * Dynamic profile appearance that changes depending on if the Terminal is
>   elevated or not. ([#1939], [#8311])


## PR Checklist
* [x] Specs: #1032, #632
* [x] References: #5000, #4472, #2227, #7240, #8135, #8311
* [x] I work here

## Detailed Description of the Pull Request / Additional comments
_\*<sup>\*</sup><sub>\*</sub> read the spec  <sub>\*</sub><sup>\*</sup>\*_

### Why are these two separate documents?

I felt that the spec that is currently in review in #7240 and this doc should remain separate, yet closely related documents. #7240 is more about showing how this large set of problems discussed in #5000 can all be solved technically, and how those solutions can be used together. It establishes that none of the proposed solutions for components of #5000 will preclude the possibility of other components being solved. What it does _not_ do however is drill too deeply on the user experience that will be built on top of those architectural changes. 

This doc on the other hand focuses more closely on a pair of scenarios, and establishes how those scenarios will work technically, and how they'll be exposed to the user.
2021-08-25 12:42:55 -05:00
PankajBhojwani
1b6e6bd6dd Fix setting wght axis font bugs (#10863)
- When deciding whether to call `_AnalyzeFontFallback`, also check if the user set any font axes
- Do not use the user set weight if we are setting the weight due to the bold attribute
- When calling `FontFaceWithAttribute`, check if the user set the italic axis as well as the text attribute

* [x] Closes #10852
* [x] Closes #10853
2021-08-25 01:19:40 +00:00
Dustin L. Howett
7b6df26411 Move commandline->title promotion into TerminalSettings (#11029)
It was insufficient to only promote commandline components to titles
during commandline parsing, because we also have a whole complement of
actions that contain NewTerminalArgs. The tests caught me out a little
too late (sorry!). I decided it was better move promotion down to
TerminalSettings.

Fixes #6776
Re-implements #10998
2021-08-24 23:31:27 +00:00
Dustin L. Howett
f3cc4c0328 Revert "Upgrade to Microsoft.UI.Xaml 2.6.2 (or equivalent) (#10996)" (#11031)
The upgrade to 2.6 revealed #11003 and Microsoft/microsoft-ui-xaml#5435, and is impeding
progress on PGO.

This reverts commit cfdf03c24b.
Reverts microsoft/terminal#10996
2021-08-24 17:46:12 -05:00
Kayla Cinnamon
f3a49fafe3 Actions page design spec (#9427) 2021-08-24 14:03:14 -07:00
Leonard Hecker
15c02b77a0 Remove std::deque from Renderer (#10923)
This commit improves the renderer classes by:
* reducing binary size by 4kB
* improving performance by 5%
* reducing code complexity

## References

* #10563 -- vtebench tracking issue

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

## Validation Steps Performed

* Ran vtebench/termbench and noted ~5% perf. improvements
2021-08-24 15:27:59 +00:00
Schuyler Rosefield
2c3368f766 Fix directional movement during startup (#11023)
During startup we do not have real dimensions, so we have to guess what
our dimensions should be based off of the splits.

We'll augment the state of the pane search to also have a size in each
dimension that gets incrementally upgraded as we recurse through the
tree.

References #10978
2021-08-24 15:27:21 +00:00
PankajBhojwani
b1131263cf Fix alt+space opening system menu and sending keys to terminal (#10988)
If both of the following are true

1. alt+space is not explicitly unbound
2. alt+space is not bound to a command

Then the window procedure will handle the alt+space to open up the context menu.
In this case, we need to make sure we don't send the keys to terminal.

Closes #10935
2021-08-24 14:07:45 +00:00
Carlos Zamora
c53fe1c2bf Fix failing UIA movement tests (#10991)
## Summary of the Pull Request
Follow-up for #10886. The new UIA movement tests found some failing cases. This PR fixes UiaTextRangeBase to have movement match that of MS Word. In total, this fixes 64 tests.

## PR Checklist
* [X] Closes #10924
* [X] Tests added/passed

## Detailed Description of the Pull Request / Additional comments
Root causes include...
1. if we were a non-degenerate range and we failed to move, we should still expand to enclose the unit
2. non-degenerate ranges are treated as if they already encompassed their given unit.
   - this one is a bit difficult to explain. Consider these examples:
      1. document movement
         - state: you have a 1-cell wide range on the buffer, and you try to move by document
         - result: move by 0 (there is no next/prev document), but the range now encompasses the entire document
      2. line movement
         - state: you have a 1-cell wide range on a line, and you try to move back by a line
         - result: you go to the previous line (not the beginning of this line)
   - conversely, a degenerate range successfully moves to the beginning/end of the current unit (i.e. document/line)
   - this (bizarre) behavior was confirmed using MS Word

As a bonus, occasionally, Narrator would get stuck when navigating by line. This issue now seems to be fixed.

## Updates to existing tests
- `CanMoveByCharacter`
   - `can't move backward from (0, 0)` --> misauthored, result should be one character wide.
   - `can't move past the last column in the last row` --> misauthored and already covered in generated tests
- `CanMoveByLine`
   - `can't move backward from top row` --> misauthored, end should be on next line. Already covered by generated tests
   - `can't move forward from bottom row` --> misauthored, end should be on next line
   - `can't move backward when part of the top row is in the range` --> misauthored, should expand
   - `can't move forward when part of the bottom row is in the range` --> misauthored, degenerate range moves to end of buffer
- `MovementAtExclusiveEnd`
   - populate the text buffer _before_ we do a move by word operation
   - update to match the now fixed behavior
2021-08-24 13:56:38 +00:00
Mike Griese
f9a844dbda Lookup WSL distros in the registry (#10967)
This PR converts the WSL distro generator to use the registry to lookup
WSL distros instead of trying to parse the results of `wsl.exe`.
`wsl.exe` sometimes takes a very long time to launch the WSL service,
which means that on the first launch of the Terminal, WSL distros can
sometimes be missing entirely!

## References
* Also related is #6160, but I feel that deserves a separate PR for
  warning when the default profile is a dynamic profile who's source
  indicated it was gone. 

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

## Detailed Description of the Pull Request / Additional comments

This is maybe a little BODGY, but hey we get tons of reports of this
root cause.

## Validation Steps Performed

Ran it locally, it did well. Ran a `wsl --shutdown`, then booted the
terminal - seemed to do well. I never was able to repro the slowness
myself, but I'd suspect this'll fix it.
2021-08-24 13:10:36 +00:00
Mike Griese
23a19c5818 Only focus the active pane once initialization is complete (#10978)
## Summary of the Pull Request

Since the days immemorial of the Terminal, the TermControl has auto-focused itself when it finalizes its layout. This has led to the problem that `wt ; sp ; sp ; sp...` ends up focusing one of these panes at random.

This PR fixes this issue by getting rid of the auto-focusing. Panes now manually get focused when created. We manually focus the active pane when a commandline is dispatched. since we're internally tracking "active" separate from "focused", this ends up working as you'd hope.

## References

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

## Detailed Description of the Pull Request / Additional comments

I also had to turn the cursor off by default. Most `TermControl`s would never get the `LostFocus` event, so their cursors would get left `On`, and that's not right.

## Validation Steps Performed

I've run the following things a bunch of times to make sure they work: 
* `wtd sp ; sp ; sp`
* `wtd sp ; sp ; sp ; fp -t 0`
* `newTab`
* `splitPane`
* use the command palette to do the above as well

Where the result used to be random (cases 1 & 2), the result is exactly what you'd expect now. 

It doesn't work at all for

```
wtd sp ; sp ; sp ; mf left
```

Presumably because we can't `move-focus` directionally during startup. However, that doesn't work _today_ either, so it's not making it worse. Just highlights that single scenario doesn't work right.
2021-08-24 09:49:45 +00:00
Steffen
7712104983 Refactor u8u16 and u16u8 conversion functions (#10966)
* Perform the handling of partial code points in the `u8u16` and `u16u8`
  conversion functions without preparation in a preliminary buffer.
* Simplify partials handling in `u8u16` (perf).
* Declare the parameters for the incoming data as referenced
  string_views.
* Simplify templatization.
* Simplify exception handling.

We complete the partial codepoint in the 4-bytes long cache and convert
it separately. This makes the cache ready for capturing the next
partials before the remaining string is converted. This way, we neither
need to copy the whole string into a buffer which contains complete
codepoints, nor do we need to allocate an unnecessarily long buffer
which exists for the life time of the state class instance.

Finding and capturing of partials is performed in a more linear code
using the evaluation of the length of a code point.

The parameters for the incoming data are now explicitely declared to be
referenced string_views.

`CATCH_RETURN` is used to improve the readability of the code.

## Validation Steps Performed
* manually tested
* unit tests passed

Closes #10946

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
2021-08-23 23:48:13 +00:00
Leonard Hecker
608a49e817 Allow generated profiles to be deleted (#11007)
Re-enables the delete button for generated profiles in the settings UI.
Additionally fixes "Startup Profiles" to only list active profiles.

Profiles are considered deleted if they're absent from settings.json, but their
GUID has been encountered before. Or in other words, from a user's perspective:
Generated profiles are added to the settings.json automatically only once.
Thus if the user chooses to delete the profile (e.g. using the delete button)
they aren't re-added automatically and thus appear to have been deleted.

Meanwhile those generated profiles are actually only marked as "hidden"
as well as "deleted", but still exist in internal profile lists.
The "hidden" attribute hides them from all existing menus. The "deleted" one
hides them from the settings UI and prevents them from being written to disk.

It would've been preferrable of course to just not generate and
add deleted profile to internal profile lists in the first place.
But this would've required far more wide-reaching changes.
The settings UI for instance requires a list of _all_ profiles in order to
allow a user to re-create previously deleted profiles. Such an approach was
attempted but discarded because of it's current complexity overhead.

## References

* Part of #9997
* A sequel to 5d36e5d

## PR Checklist

* [x] Closes #10960
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

* "Startup Profiles" doesn't list deleted profiles ✔️
* Manually removing an item from settings.json removes the profile ✔️
* Removing cmd.exe and saving doesn't create empty objects (#10960) ✔️
* "Add a new profile" lists deleted profiles ✔️
* "Duplicate" recreates previously deleted profiles ✔️
* Profiles are always created with GUIDs ✔️
2021-08-23 22:00:08 +00:00
Dustin L. Howett
10992b77a0 Only iterate panes one time when updating settings (#10997)
The original code for settings reload iterated the entire tree of panes
for every profile in the new settings (O(mn)) and constructed a
TerminalSettings object for every profile even if it later went unused.

This implementation:

1. Collects all new profiles keyed by guid
1.a. Adds the "defaults" profile to the map
2. Iterates every pane, just once, and updates its profile if it shows
   up in the list by GUID.

I've merged all of the per-tab code into a single loop.

Because of 1.a., this code can now update panes that are hosting the
"base" profile.
2021-08-23 19:20:08 +00:00
Dustin L. Howett
f6f5598c9c Rely more on profile objects and less on GUIDs (#10982)
Right now, we store GUIDs in panes and most of the functions for interacting
with profiles on the settings model take GUIDs and look up profiles.

This pull request changes how we store and look up profiles to prefer profile
objects. Panes store strong references to their originating profiles, which
simplifies settings lookup for CloseOnExit and the bell settings. In fact,
deleting a pane's profile no longer causes it to forget which CloseOnExit
setting applies to it. Duplicating a pane that is hosting a deleted profile
(#5047) now duplicates the profile, even though it is otherwise unreachable.

This makes the world more consistent and allows us to _eventually_ support panes
hosting profiles that do not have GUIDs that can be looked up in the profile
list. This is a gateway to #6776 and #10669, and consolidating the profile
lookup logic will help with #10952.

PR #10588 introduced TerminalSettings::CreateWithProfile and made
...CreateWithProfileByID a thin wrapper over top it, which looked up the profile
by GUID before proceeding. It has also been removed, as its last caller is gone.

Closes #5047
2021-08-23 12:11:53 -05:00
Dustin L. Howett
f681d3a1c1 When there's no profile or title, invent a title from the commandline (#10998)
This supports a future world where we give commandline-only invocations
their own tabs. It was easier to promote the commandline to a title at
the time of argument parsing, rather than later, but I am happy to
change this if anyone disagrees.
2021-08-23 17:01:04 +00:00
Dustin Howett
d07546a6fe Renormalize line endings on TerminalSettingsEditor's resw 2021-08-23 11:54:05 -05:00
Matthew
ed7c716978 Add titlebar acrylic (#10864)
Add support for acrylic in the titlebar

## PR Checklist
* [x] CLA signed
## Detailed Description of the Pull Request / Additional comments
This seems to be a highly requested feature and seeing as #5772 was closed I thought it made sense to make a PR for this.
![image](https://user-images.githubusercontent.com/40522069/128095309-f9073a9d-274c-44a1-be5b-34ea58d5a5a9.png)

## Validation Steps Performed
Checked that acrylic works in both dark and light modes and switching between them still works. Also checked that acrylic in the tab row still works when tabs in titlebar is disabled.
2021-08-23 16:40:25 +00:00
Leon Liang
0c901edd81 Create a window process for the tray icon (#10980)
Currently, the monarch window will show itself when opening the tray icon context menu. This is because a window must be set as the foreground window when the context menu opens, otherwise the menu won't be able to be dismissed when clicking outside of the context menu.

This PR makes the tray icon create a non visible/interactable window for the sole purpose of being set as the foreground window when the tray icon's context menu is opened. Then none of the terminal windows should be set as the foreground window when opening the context menu.

Closes #10936
2021-08-20 23:24:13 +00:00
Mike Griese
acf1ddc9c4 Don't scroll vertically on horizontal scroll motions (#10979)
## Summary of the Pull Request

Pretty straightforward. Check if the scroll event is a horizontal movement. If it is, ignore it. We don't have a horizontal scrollbar.

## References
* obviously, revisit this if we ever do #1860 

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

## Validation Steps Performed
* scrolled ↑/↓ with slaptop trackpad: terminal scrolls.
* scrolled ←/→ with slaptop trackpad: terminal doesn't scroll.
* Scrolling _slightly more vertically than horizontally_ still scrolls.
* Scrolling _slightly more horizontally than vertically_ doesn't scroll.
2021-08-20 22:58:45 +00:00
PankajBhojwani
cb2f347c2f Fix text selection while new lines are being printed when history buffer is full (#10749)
When our text buffer is full, newlines cause the buffer to scroll underneath the viewport (rather than the viewport moving down). This was causing selections made during text output to scroll down. To solve this, when we increment the circular buffer, we decrement the y-coordinates of the current selections by 1. We also invalidate the previous selection rects.

Closes #10319
2021-08-20 22:36:25 +00:00
PankajBhojwani
49874d1b9e Reword bold enum options (#10969)
## Summary of the Pull Request
Reword the bold enum options for clarity

## PR Checklist
* [x] Closes #10955
2021-08-20 22:34:33 +00:00
Dustin L. Howett
cfdf03c24b Upgrade to Microsoft.UI.Xaml 2.6.2 (or equivalent) (#10996)
This commit moves us from MUX 2.5 to MUX 2.6. I have temporarily
disabled the new control styles in `TerminalApp\App.xaml` by setting
`ControlsResourcesVersion` to `Version1`. There is no significant expected
visual impact.

Closes #10508
2021-08-20 20:41:03 +00:00
Leonard Hecker
70d44c84c8 Make ActionMap compatible with ScanCode-only KeyChords (#10945)
This commit partially reverts d465a47 and introduces an alternative approach by adding Hash and Equals methods to the KeyChords class. Those methods will now favor any existing Vkeys over ScanCodes.

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

## Validation Steps Performed

* Added a new test, which is ✔️
* Various standard commands still work ✔️
* Hash() returns the same value for all KeyChords that are Equals() ✔️
2021-08-20 00:21:33 +00:00
Carlos Zamora
1678b58dde Improve UIA movement testing methodology (#10886)
Introduces a new methodology to maintain tests for UI Automation. This includes...
- `UiaTests.csv`: an excel spreadsheet designed to store UIA movement tests in a compact format
- `GeneratedTests.ps1`: a PowerShell script that imports `UiaTests.csv` and outputs a C++ TEST_METHOD for `UiaTextRangeTests.

This new system can be used to easily add more UIA movement tests.

Read https://github.com/microsoft/terminal/blob/dev/cazamor/a11y-7000/testing/tools/TestTableWriter/README.md for more details.

Follow-up work items:
- #10924 **Failing Tests**: this found some failing tests. We should make them not fail.
- #10925 **Missing Tests: Word navigation**: Word navigation is missing.
- #10926 **MoveEndpoint Tests**: an additional column can be added to the CSV "EndpointTarget", which can be "start", "end", or "both". This will allow us to test `MoveEndpoint` in addition to `Move`.
2021-08-19 20:47:07 +00:00
Leon Liang
482dcec60a Tray Icon PR followup (#10938)
Some followups to #10368:
- Accidentally reverted a defapp change where the Monarch should not by default register itself as a handoff server.
- Destroy the tray icon if we're a monarch otherwise if we're a quake window we request the monarch to hide the icon.
2021-08-19 17:38:18 +00:00
Don-Vito
46fd7caf5a Fix focus-tab --previous/next to ignore tab switcher order (#10947)
When creating `startupAction` use `TabSwitcherMode::Disabled` in action args
to disable the tab switcher and prevent MRU logic to be applied.

Closes #10070
2021-08-19 12:18:14 -05:00
Carlos Zamora
638c6d0291 Ensure automation peer is created regardless of terminal initialization (#10971)
## Summary of the Pull Request
The bug was that Narrator would still read the content of the old tab/pane although a new tab/pane was introduced. This is caused by the automation peer not being created when XAML requests it. Normally, we would prevent the automation peer from being created if the terminal was not fully initialized.

This change allows the automation peer to be created regardless of the terminal being fully initialized by...
- `TermControl`: `_InitializeTerminal` updates the padding (dependent on the `SwapChainPanel`) upon full initialization
- `ControlCore`: initialize the `_renderer` in the ctor so that we can attach the UIA Engine before `ControlCore::Initialize()` is called (dependent on `SwapChainPanel` loading)

As a bonus, this also fixes a locking issue where logging would attempt to get the text range's text and lock twice. The locking fix is very similar to #10937.

## PR Checklist
Closes [MSFT 33353327](https://microsoft.visualstudio.com/OS/_workitems/edit/33353327)

## Validation Steps Performed
- New pane from key binding is announced by Narrator
- New tab from key binding is announced by Narrator
2021-08-18 21:26:43 +00:00
Schuyler Rosefield
68294f863d GH10909 in order movement (#10927)
Adds new in-order traversal for MoveFocus and SwapPane actions.
Refactors the Pane methods to share a `NavigateDirection`
implementation.

Closes #10909

A large amount of the churn here is just renaming some of the things for
directional movement to reflect that it might not always be based on the
focused pane. `NextPane` and `PreviousPane` are the functions that
actually select the next/previous pane respectively and are the core
component of this PR.

VALIDATION
Created multiple panes on a tab, and tried both forward and backwards
movements with move-focus and swap-pane.
2021-08-16 22:33:23 +00:00
PankajBhojwani
59f184aa2d Render "intense" text as bright by default (#10958)
From discussion at #10678, we will ship with "intense" as bright for now until we fix text getting cut off by some bold fonts.
2021-08-16 19:59:37 +00:00
Mike Griese
a544f56e17 Add an ENUM setting for disabling rendering "intense" text as bold (#10759)
## Summary of the Pull Request

This adds a new setting `intenseTextStyle`. It's a per-appearance, control setting, defaulting to `"all"`.
* When set to `"all"` or `["bold", "bright"]`, then we'll render text as both **bold** and bright (1.10 behavior)
* When set to `"bold"`, `["bold"]`, we'll render text formatted with `^[[1m` as **bold**, but not bright
* When set to `"bright"`, `["bright"]`, we'll render text formatted with `^[[1m` as bright, but not bold. This is the pre 1.10 behavior
* When set to `"none"`, we won't do anything special for it at all. 

## references
* I last did this in #10648. This time it's an enum, so we can add bright in the future. It's got positive wording this time.
* ~We will want to add `"bright"` as a value in the future, to disable the auto intense->bright conversion.~ I just did that now.
* #5682 is related

## PR Checklist
* [x] Closes #10576 
* [x] I seriously don't think we have an issue for "disable intense is bright", but I'm not crazy, people wanted that, right? https://github.com/microsoft/terminal/issues/2916#issuecomment-544880423 was the closest
* [x] I work here
* [x] Tests added/passed
* [x] https://github.com/MicrosoftDocs/terminal/pull/381

## Validation Steps Performed

<!-- ![image](https://user-images.githubusercontent.com/18356694/125480327-07f6b711-6bca-4c1b-9a76-75fc978c702d.png) -->
![image](https://user-images.githubusercontent.com/18356694/128929228-504933ee-cf50-43a2-9982-55110ba39191.png)


Yea that works. Printed some bold text, toggled it on, the text was no longer bold. hooray.


### EDIT, 10 Aug

```json
"intenseTextStyle": "none",
"intenseTextStyle": "bold",
"intenseTextStyle": "bright",
"intenseTextStyle": "all",
"intenseTextStyle": ["bold", "bright"],
```

all work now. Repro script:
```sh
printf "\e[1m[bold]\e[m[normal]\e[34m[blue]\e[1m[bold blue]\e[m\n"
```
2021-08-16 13:45:56 +00:00
Mike Griese
29be8564f6 Manually dismiss popups when the window moves, or the SUI scrolls (#10922)
## Summary of the Pull Request

BODGY!

This solution was suggested in https://github.com/microsoft/microsoft-ui-xaml/issues/4554#issuecomment-887815332.

When the window moves, or when a ScrollViewer scrolls, dismiss any popups that are visible. This happens automagically when an app is a real XAML app, but it doesn't work for XAML Islands.

## References
* upstream at https://github.com/microsoft/microsoft-ui-xaml/issues/4554

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

## Detailed Description of the Pull Request / Additional comments

Unfortunately, we've got a bunch of scroll viewers in our SUI. So I did something bodgyx2 to make our life a little easier.

`DismissAllPopups` can be used to dismiss all popups for a particular UI element. However, we've got a bunch of pages with scroll viewers that may or may not have popups in them. Rather than define the same exact body for all their `ViewChanging` events, the `HasScrollViewer` struct will just do it for you!

Inside the `HasScrollViewer` stuct, we can't get at the `XamlRoot()` that our subclass implements. I mean, _we_ can, but when XAML does it's codegen, _XAML_ won't be able to figure it out.

Fortunately for us, we don't need to! The sender is a UIElement, so we can just get _their_ `XamlRoot()`.

So, you can fix this for any SUI page with just a simple 

```diff
-    <ScrollViewer>
+    <ScrollViewer ViewChanging="ViewChanging">
```

```diff
-    struct AddProfile : AddProfileT<AddProfile>
+    struct AddProfile : public HasScrollViewer<AddProfile>, AddProfileT<AddProfile>
```

## Validation Steps Performed

* the window doesn't close when you move it
* the popups _do_ close when you move the window
* the popups close when you scroll any SUI page
2021-08-16 13:41:17 +00:00
Leonard Hecker
5d36e5d2df Hide profiles by default if they aren't new (#10910)
Let's say a user doesn't know that they need to write `"hidden": true` in
order to prevent a profile from showing up (and a settings UI doesn't exist).
Naturally they would open settings.json and try to remove the profile object.
This section of code recognizes if a profile was seen before and marks it as
`"hidden": true` by default and thus ensures the behavior the user expects:
Profiles won't show up again after they've been removed from settings.json.

## References

#8324 - Application State

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

## Validation Steps Performed

* settings.json/state.json are created if they don't exist ✔️
* Removing any profile from settings.json doesn't cause it to appear again ✔️
* Hitting save in SUI creates profiles with `"hidden": true` ✔️
* Removing a default profile and hitting save in SUI works 
  An empty object is added instead.
2021-08-16 13:32:05 +00:00
Carlos Zamora
0220f71883 Prevent deadlock in UIA Move API (#10937)
Fixes a bug where interacting with Windows Terminal when using Narrator causes Windows Terminal to hang.

`UiaTextRangeBase::Move()` locks, but later calls `UiaTextRangeBase::ExpandToEnclosingUnit()` which attempts to lock again. The workaround for this is to introduce a `_expandToEnclosingUnit()` that _does not_ lock the console. Then, `Move()` calls this new method, thus only allowing one lock to be established at a time.

This bug is observed to be in v1.11.2221.0 and _not_ in v1.9.1942.0.
2021-08-13 17:56:34 +00:00
Don-Vito
70560a789c Change settings content frame transition to drill in (#10934)
## PR Checklist
* [x] Closes #10632
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.
2021-08-12 22:36:10 +00:00
Leon Liang
a0edb12cd6 Add Minimize to Tray and Tray Icon (#10368)
A brief summary of the behavior of the tray icon:
- There will only ever be one tray icon representing all windows.
- Left-Click on a Tray Icon brings up the MRU window.
- Right-Click on a Tray Icon brings up a Context Menu:
```
Focus Terminal
----------------
Windows --> Window ID 1 - <unnamed window>
            Named Window
            Named Window Again
 ```
- Focus Terminal will bring up the MRU window.
- Clicking on any of the Window "names" in the submenu will summon the window.

## Settings Changes

Two new global settings are introduced: `alwaysShowTrayIcon` and `minimizeToTray`. Here's a chart explaining the behavior with the two settings.

|                      | `alwaysShowTrayIcon:true`                                          | `alwaysShowTrayIcon:false`                                         |
|----------------------|------------------------------------------------------------------|------------------------------------------------------------------|
| `minimizeToTray:true`  | tray icon is always shown. minimize button will hide the window. | tray icon is always shown. minimize button will hide the window. |
| `minimizeToTray:false` | tray icon is always shown.                                       | tray icon is not shown ever.                                     |

Closes #5727

## References
[Spec for Minimize to Tray](https://github.com/microsoft/terminal/blob/main/doc/specs/%23653%20-%20Quake%20Mode/%23653%20-%20Quake%20Mode.md#minimize-to-tray)
Docs PR - MicrosoftDocs/terminal#352
#10448 - My list of TODOs
2021-08-12 19:54:39 +00:00
Leonard Hecker
d3f9859051 Improve WriteCharsLegacy performance by increasing local buffer size (#10921)
Improve WriteCharsLegacy performance by increasing LocalBuffer size, allowing
longer runs of characters to be submitted to the remaining parts of conhost.

References #10563 -- vtebench tracking issue

## Validation Steps Performed

* Ran `cat big.txt`, vtebench and termbench and
  noted ~5% performance improvements
2021-08-12 17:54:59 +00:00
Don-Vito
f1dc649135 Fix WriteUTF8FileAtomic to preserve symlinks (#10908)
WriteUTF8FileAtomic  overrides the content of the file "atomically"
by creating a temp file and then renaming it to the original path.
The problem arises when the original path is symbolic link,
as the link itself gets overridden by a file (rather than the link target).
This PR introduces a special handling of the symlinks:
if the path as a symlink we resolve the path and use:
1. target's directory to create a temp-file in
2. target itself to be replaced with the tempfile.

Symlink resolution is problematic when the target path does not exist,
as there is no good utility that resolves such link (canonical() fails).
In this corner case we skip the "atomic" approach of renaming the file
and write the link target directly.

Closes #10787
2021-08-12 16:47:16 +00:00
Schuyler Rosefield
9eb9bc9235 Move Pane to Tab (GH7075) (#10780)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Add functionality to move a pane to another tab. If the tab index is greater than the number of current tabs a new tab will be created with the pane as its root. Similarly, if the last pane on a tab is moved to another tab, the original tab will be closed.

This is largely complete, but I know that I'm messing around with things that I am unfamiliar with, and would like to avoid footguns where possible. 

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

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #7075
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [x] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [x] Schema updated.
* [ ] 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
Things done:
- Moving a pane to a new tab appears to work. Moving a pane to an existing tab mostly works. Moving a pane back to its original tab appears to work.
- Set up {Attach,Detach}Pane methods to add or remove a pane from a pane. Detach is slightly different than Close in that we want to persist the tree structure and terminal controls.
- Add `Detached` event on a pane that can be subscribed to to remove other event handlers if desired. 
- Added simple WalkTree abstraction for one-off recursion use cases that calls a provided function on each pane in order (and optionally terminates early).
- Fixed an in-prod bug with closing panes. Specifically, if you have a tree (1; 2 3) and close the 1 pane, then 3 will lose its borders because of these lines clearing the border on both children https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/Pane.cpp#L1197-L1201 .

To do:
- Right now I have `TerminalTab` as a friend class of `Pane` so I can access some extra properties in my `WalkTree` callbacks, but there is probably a better choice for the abstraction boundary.

Next Steps:
- In a future PR Drag & Drop handlers could be added that utilize the Attach/Detach infrastructure to provide a better UI.
- Similarly once this is working, it should be possible to convert an entire tab into a pane on an existing tab (Tab::DetachRoot on original tab followed by Tab::AttachPane on the target tab).
- Its been 10 years, I just really want to use concepts already.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manual testing by creating pane(s), and moving them between tabs and creating new tabs and destroying tabs by moving the last remaining pane.
2021-08-12 16:41:17 +00:00
Leonard Hecker
d465a47bc5 Fix layering of sc() keybindings with vk() ones (#10917)
The quake mode keybinding is bound to a scancode. This made it
impossible to override it with a vkey-based one like "win+\`".
This commit fixes the issue by making sure that a `KeyChord` always has a vkey,
and leveraging this fact inside ActionMap, which now ignores the scan-code.

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

## Validation Steps Performed

* quake mode and other keybinding still work ✔️
* Repro settings from #10875 work correctly ✔️
2021-08-11 23:09:25 +00:00
Mike Griese
9c858cd5b8 Add logging, test for #10875 (#10907)
## Summary of the Pull Request

This isn't a fix for #10875, but it is logging that help identify the root cause here. The logging may additionally be helpful for some of the other issues we're seeing elsewhere in the repo, namely #10340. 

@lhecker is actually working on the fix for #10875, so hopefully this test will help validate.

## References
* Regressed in #10666.
* logging for #8888

## PR Checklist
* [x] Closes nothing
* [x] I work here
* [x] Tests added, and they absolutely fail, but they're localtests, so ¯\\\_(ツ)_/¯
* [n/a] Requires documentation to be updated

## details

While I was here, I noticed that `KeyBindingsTests::KeyChords` has been broken for some time now. So I fixed that too.
2021-08-11 15:20:15 +00:00
Leonard Hecker
42bf605e1c Use STL for ActionMap members (#10916)
My first approach to solve #10875 failed.
This PR contains the most useful change as a separate commit.

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

## Validation Steps Performed

* quake mode keybinding works ✔️
* command palette still works ✔️
2021-08-11 15:18:56 +00:00
Mike Griese
121fb739fd Initialize the padding for the Control UIA provider (#10874)
## Summary of the Pull Request

This was missed in #10051. We need to make sure that the UIA provider can immediately know about the padding in the control, not just after the settings reload.

## PR Checklist
* [x] Closes #9955.e
  * [x] Additionally, this just closes #9955. The only remaining box in there never repro'd, so probably wasn't even root caused by #9820. I think we can close that issue for now, and reactivate if something else was broken.
* [x] I work here
* [ ] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed

Checked before/after in Accessibility Insights. Before the row rectangles were the full width of the control initially. Now they're properly padded.
2021-08-11 15:13:38 +00:00
Floris Westerman
ebf41dd6b2 Adding/fixing Alt+Space handling (#10799)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
This PR implements/solves #7125. Concretely: two requests regarding alt+space were posted there:
1. Disabling the alt+space menu when the keychord explicitly unbound - and forwarding the keystroke to the terminal
2. Disabling the alt+space menu when the keychord is bound to an action

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

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #7125
* [x] CLA signed.
* [x] Tests added/passed
* [x] Documentation updated. N/A
* [x] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan.
The issue was marked Help-Wanted. I am happy to change the implementation to better fit your (planned) architecture.

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

While researching the solution, I noticed that the XAML system was always opening the system menu after Alt+Space, even when explicitly setting the event to be handled according to the documentation. The only solution I could find was to hook into the "XAML bypass" already in place for F7 KeyDown, and Alt KeyUp keystrokes. This bypass sends the keystroke to the AppHost immediately. This bypass method will "fall back" to the normal XAML routing when the keystroke is not handled.

The implemented behaviour is as follows:
- Default: same as normal; system menu is working since the bypass does not handle the keystroke
- Alt+Space explicitly unbound: bypass passes the keystroke to the terminal and marks it as handled
- Alt+Space bound to command: bypass invokes the command and marks it as handled

Concretely, added a method to the KeyBindings and ActionMap interfaces to check whether a keychord is explicitly unbound. The implementation for `_GetActionByKeyChordInternal` already distinguishes between explicitly unbound and lack of binding, however this distinction is not carried over to the public methods. I decided not to change this existing method, to avoid breaking other stuff and to make the API more explicit.

Furthermore, there were some checks against Alt+Space further down in the code, preventing this keystroke from being entered in the terminal. Since the check for this keystroke is now done at a "higher" level, I thought I could safely remove these checks as otherwise the keystroke could never be sent to the terminal itself. Please correct me if I'm wrong.

Note that when alt+space is bound to an action that opens the command pallette (such as tab search), then a second press of the key combination does still open the system menu. This is because at that point, the "bypass" is cancelled (called "not a good implementation" in #4031). I don't think this can easily be solved for now, but this is a very minor bug/inconvenience.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Added tests for the new method. Performed manual checking:
* [x] Default configuration still opens system menu like normal
* [x] Binding alt+space to an action performs the action and does not show the system menu
* [x] Explicitly unbinding alt+space no longer shows the system menu and sends the keystroke to the terminal. I was unable to run the debug tap (it crashed my instance - same thing happening on preview and release builds) to check for sure, but behaviour was identical to native linux terminals.
2021-08-10 19:53:07 +00:00
Mike Griese
a14b6f89f6 Combine progress states in the tab, taskbar (#10755)
## Summary of the Pull Request
![background-progress-000](https://user-images.githubusercontent.com/18356694/126653006-3ad2fdae-67ae-4cdb-aa46-25d09217e365.gif)

This PR causes the Terminal to combine taskbar states at the tab and window level, according to the [MSDN docs for `SetProgressState`](https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group). 

This allows the Terminal's taskbar icon to continue showing progress information, even if you're in a pane/tab that _doesn't_ have progress state. This is helpful for cases where the user may be running a build in one tab, and working on something else in another.

## References

* [`SetProgressState`](https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group)
* Progress mega: #6700 

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

## Detailed Description of the Pull Request / Additional comments

This also fixes a related bug where transitioning from the "error" or "warning" state directly to the "indeterminate" state would cause the taskbar icon to get stuck in a bad state.

## Validation Steps Performed

<details>
<summary><code>progress.cmd</code></summary>

```cmd
@echo off
setlocal enabledelayedexpansion

set _type=3
if (%1) == () (
    set _type=3
) else (
    set _type=%1
)



if (%_type%) == (0) (
    <NUL set /p =]9;4
    echo Cleared progress
)
if (%_type%) == (1) (
    <NUL set /p =]9;4;1;25
    echo Started progress (normal, 25^)
)
if (%_type%) == (2) (
    <NUL set /p =]9;4;2;50
    echo Started progress (error, 50^)
)
if (%_type%) == (3) (
    @rem start indeterminate progress in the taskbar
    @rem this `<NUL set /p =` magic will output the text _without a newline_

    <NUL set /p =]9;4;3
    echo Started progress (indeterminate, {omitted})
)
if (%_type%) == (4) (
    <NUL set /p =]9;4;4;75
    echo Started progress (warning, 75^)
)

```

</details>
2021-08-10 11:16:17 +00:00
Mike Griese
c55888f88d Make the TerminalApi exception handler less garrulous (#10901)
## Summary of the Pull Request

Apparently the exception handler in TerminalApi is far too talkative. We're apparently throwing in `TerminalApi::CursorLineFeed` way too often, and that's caused an internal bug to be filed on us.

This represents making the event less talkative, but doesn't actually fix the bug. It's just easier to get the OS bug cleared out quick this way. 

## References
* MSFT:33310649

## PR Checklist
* [x] Fixes the **A** portion of #10882, which closes MSFT:33310649
* [x] I work here
* [n/a] Tests added/passed
* [n/a] Requires documentation to be updated
2021-08-09 18:28:06 +00:00
Mike Griese
7acec306a6 Account for the window frame when calculating initial position (#10902)
## Summary of the Pull Request

Turns out, we'd only ever use the non-client size to calculate the size of the window, but not the actual position. As we learned in #10676, the nonclient area extends a few pixels past the visible borders of the window. 

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

## Validation Steps Performed
* [x] Works with the `IslandWindow`
* [x] Works with the `NonClientIslandWindow`
2021-08-09 18:27:20 +00:00
Don-Vito
cd4aabda84 Prevent redraw upon resize if new size is equal to old (#10895)
## Summary of the Pull Request
Do not invoke terminal resize logic if view port dimensions didn't change

## PR Checklist
* [x] Closes #10857 
* [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
Short-circuit `ControlCore::_doResizeUnderLock` if the dimensions of the
required view port are equal to the dimensions of the current view port
2021-08-09 18:22:08 +00:00
Carlos Zamora
fdffa24a71 Update SUI tooltips from 'checked' to 'enabled' (#10885)
Updates the Settings UI tooltips to use "enabled" and "disabled" instead of "checked" and "unchecked" respectively.

Closes #10814
2021-08-09 17:29:04 +00:00
Mike Griese
9f2d40614b Allow ThrottledFunc to work on different types of dispatcher (#10187)
#### ⚠️ targets #10051

## Summary of the Pull Request

This updates our `ThrottledFunc`s to take a dispatcher parameter. This means that we can use the `Windows::UI::Core::CoreDispatcher` in the `TermControl`, where there's always a `CoreDispatcher`, and use a `Windows::System::DispatcherQueue` in `ControlCore`/`ControlInteractivity`. When running in-proc, these are always the _same thing_. However, out-of-proc, the core needs a dispatcher queue that's not tied to a UI thread (because the content proces _doesn't have a UI thread!_). 

This lets us get rid of the output event, because we don't need to bubble that event out to the `TermControl` to let it throttle that update anymore. 

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

## PR Checklist
* [x] This is a part of #1256
* [x] I work here
* [n/a] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

Fortunately, `winrt::resume_foreground` works the same on both a `CoreDispatcher` and a `DispatcherQueue`, so this wasn't too hard!

## Validation Steps Performed

This was validated in `dev/migrie/oop/the-whole-thing` (or `dev/migrie/oop/connection-factory`, I forget which), and I made sure that it worked both in-proc and x-proc. Not only that, _it wasn't any slower_!This reverts commit 04b751faa7.
2021-08-09 15:21:59 +00:00
James Holderness
90ff261c35 Add support for downloadable soft fonts (#10011)
This PR adds conhost support for downloadable soft fonts - also known as
dynamically redefinable character sets (DRCS) - using the `DECDLD`
escape sequence.

These fonts are typically designed to work on a specific terminal model,
and each model tends to have a different character cell size. So in
order to support as many models as possible, the code attempts to detect
the original target size of the font, and then scale the glyphs to fit
our current cell size.

Once a font has been downloaded to the terminal, it can be designated in
the same way you would a standard character set, using an `SCS` escape
sequence. The identification string for the set is defined by the
`DECDLD` sequence. Internally we map the characters in this set to code
points `U+EF20` to `U+EF7F` in the Unicode private use are (PUA).

Then in the renderer, any characters in that range are split off into
separate runs, which get painted with a special font. The font itself is
dynamically generated as an in-memory resource, constructed from the
downloaded character bitmaps which have been scaled to the appropriate
size.

If no soft fonts are in use, then no mapping of the PUA code points will
take place, so this shouldn't interfere with anyone using those code
points for something else, as along as they aren't also trying to use
soft fonts. I also tried to pick a PUA range that hadn't already been
snatched up by Nerd Fonts, but if we do receive reports of a conflict,
it's easy enough to change.

## Validation Steps Performed

I added an adapter test that runs through a bunch of parameter
variations for the `DECDLD` sequence, to make sure we're correctly
detecting the font sizes for most of the known DEC terminal models.

I've also tested manually on a wide range of existing fonts, of varying
dimensions, and from multiple sources, and made sure they all worked
reasonably well.

Closes #9164
2021-08-06 20:41:02 +00:00
Leonard Hecker
dcbf7c74f1 Reload settings when the input method changes (#10876)
`VkKeyScanW` as well as `MapVirtualKeyW` are used throughout
the project, but are input method sensitive functions.

Since #10666 `win+sc(41)` is used as the quake mode keybinding,
which is then mapped to a virtual key in order to call `RegisterHotKey`.
This mapping is highly dependent on the input method and the quake mode
key binding will fail to work once the input method was changed.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #10729
* [x] I work here
* [ ] Tests added/passed

## Validation Steps Performed

* win+` opens quake window before & after changing keyboard layout ✔️
* keyboard layout changes while WT is minimized trigger reloaded ✔️
2021-08-05 21:33:44 +00:00
Leon Liang
76793b1e3f [DefApp] Move from Monarch multi instance servers to Peasant single instance servers (#10823)
- Monarch no longer sets itself up as a `CTerminalHandoff` multi instance server by default
- In fact, `CTerminalHandoff` will only ever be a single instance server 
- When COM needs a `CTerminalHandoff`, it launches `wt.exe -embedding`, which gets picked up by the Monarch and then gets handed off to itself/peasant depending on user settings.
- Peasant now recognizes the `-embedding` commandline and will start a `CTerminalHandoff` single instance listener, and receives the connection into a new tab.

Closes #10358
2021-08-05 17:05:21 +00:00
Kayla Cinnamon
0b4839d94d Add Split Tab option to tab context menu (#10832)
<!-- 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 the Split Tab option to the tab context menu.
Clicking this option will `auto` split the active pane of the tab into a duplicate pane.
Clicking on an unfocused tab and splitting it will bring that tab into focus and split its active pane.

We could make this a flyout from the context menu to let people choose horizontal/vertical split in the future if it's requested.

I'm also wondering if this should be called Split Pane instead of Split Tab?

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

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #5025
* [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

https://user-images.githubusercontent.com/48369326/127691919-aae4683a-212a-4525-a0eb-a61c877461ed.mp4

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-08-05 13:46:24 +00:00
James Holderness
2bd4670100 Fix a use-after-free crash when returning from the alt buffer (#10878)
## Summary of the Pull Request

When switching from the alt buffer back to the main buffer, we need to copy certain cursor attributes from the one to the other. However, this copying was taking place after the alt buffer had been freed, and thus could result in the app crashing. This PR simply moves that code up a bit so it's prior to the buffer being freed.

## References

PR #10843 added the code that introduced this problem.

## PR Checklist
* [ ] Closes #xxx
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed

I was able to reproduce the crash when using a debug build, and confirmed that the crash no longer occurred after this PR was applied. I also checked that the cursor attributes were still being correctly copied back when returning from the alt buffer.
2021-08-05 13:08:51 +00:00
Michael Niksa
2eb659717c Move to 1ES engineering pools (#10854)
Move to 1ES engineering pools

## PR Checklist
* [x] Closes #10734
* [x] I work here
* [x] If the builds still work, the tests pass. (release and PR builds...)

## Validation Steps Performed
- [x] Run the builds associated with this PR
- [x] Force run a release build off this branch
- [x] Force run a PGO training build off this branch
2021-08-04 17:00:41 +00:00
Leonard Hecker
aea725f885 Fix SSE2 variant of TextColor::GetColor (#10867)
Shortly before adding the SSE2 variant I "improved" it by using
`_mm_packs_epi32`, but failed to test it again afterwards.

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

## Validation Steps Performed

* `printf "\e[mNORMAL \e[1mBOLD\n"` results in correct bold white glyphs ✔️
2021-08-04 15:57:20 +00:00
Marcel Wagner
8ab3422b57 [settings-editor] Switch to function bindings instead of Converter objects (#10846)
## Validation Steps Performed
Clicked around, validated that settings still behave the same (as far as
I can tell with my limited terminal configuration expertise)

Closes #10387
2021-08-03 22:25:23 +00:00
Ian O'Neill
cccaab8545 Fix drag and drop on '+' button for drive letters (#10842)
Fixes dragging and dropping drive letters onto the '+' button.

Manually tested - dragging and dropping the `C:\` drive onto the '+' button works when creating a new tab, splitting or creating a new window. Dragging and dropping a regular directory still works.

Closes #10723
2021-08-03 18:16:07 +00:00
Schuyler Rosefield
e7108332f7 Add the ability to toggle a pane's split direction (#10713)
<!-- 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 ability to toggle a pane's split direction
- Switch from horizontal to vertical split (and vice versa)
- Propogate new borders through to children.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Ran terminal, created multiple panes in different orientations, ran command through command palate and verified that they displayed properly in the new orientation.
2021-08-02 21:04:57 +00:00
James Holderness
9ba20805ec Sanitize C1 control chars in SetConsoleTitle API (#10847)
When the `SetContoleTitle` API is called with a title containing control
characters, we need to filter out those characters before we can forward
the title change over conpty as an escape sequence. If we don't do that,
the receiving terminal will end up executing the control characters
instead of updating the title. We were already filtering out the C0
control characters, but with this PR we're now filtering out C1 controls
characters as well.

I've simply updated the sanitizing routine in `DoSrvSetConsoleTitleW` to
filter our characters in the range `0x80` to `0x9F`. This is in addition
to the C0 range (`0x00` to `0x1F`) that was already excluded. 

## Validation Steps Performed

I've added a conpty unit test that calls `DoSrvSetConsoleTitleW` with
titles containing a variety of C0 and C1 controls characters, and which
verifies that those characters are stripped from the title forwarded to
conpty.

I've also confirmed that the test case in issue #10312 is now working
correctly in Windows Terminal.

Closes #10312
2021-08-02 21:04:17 +00:00
Leonard Hecker
94166942cc Fix font changes not resizing _invalidMap (#10856)
The `_invalidMap` size is dependent on both `clientSize` as well
as `glyphCellSize` and must be resized when either changes.

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

## Validation Steps Performed
* Changing font size with Ctrl+Mousewheel in fullscreen works ✔️
2021-08-02 20:54:46 +00:00
Dustin L. Howett
a2a605050f When launching wsl, promote the starting directory to --cd (#9223)
This commit introduces a hack to ConptyConnection for launching WSL.
When we detect that WSL is being launched (either "wsl" or "wsl.exe",
unqialified or _specifically_ from the current OS's System32 directory),
we will promote the startingDirectory specified at launch time into a
commandline argument.

Why do we want to switch to `--cd`?
With the current design of ConptyConnection and WSL, there are some
significant limitations:
* `startingDirectory` cannot be a WSL path, which forces users to
  use weird tricks such as setting the starting directory to
  `\\wsl$\Distro\home\user`.
* WSL occasionally fails to launch in time to handle a `\\wsl$` path,
  which makes us spawn in a strange location (or no location at all).

(This fix will only address the second one until a WSL update is
released that adds support for `--cd $LINUX_PATH`.)

We will not do the promotion if any of the following are true:
* the commandline contains `--cd` already
* the commandline contains a bare `~`
   * This was a commonly-used workaround that forced wsl to start in the
     user's home directory. It conflicts with --cd.
* wsl is not spelled properly (`WSL` and `WSL.EXE` are unacceptable)
* an absolute path to wsl outside the system32 directory is provided

We chose the do this trick in the connection layer, the latest possible
point, because it captures the most use cases.

We could have done it earlier, but the options were quite limiting.
They are:

* Generate WSL profiles with startingDirectory set to the home folder
   * We can't do this because we do not know the user's home folder
     path.
* Generate WSL profiles with `--cd` in them.
   * This only works for unmodified profiles.
   * This only works for generated profiles.
   * Users cannot override the commandline without breaking it.
   * Users cannot specify a startingDirectory (!) since the one on the
     commandline wins.
* Set a flag on generated WSL profiles to request this trick
   * This only works for generated profiles. Users who create their own
     WSL profiles couldn't set startingDirectory and have it work the
     same.

Patching the commandline, hacky though it may be, seemed to be the most
compatible option. Eventually, we can even support `wt -d ~ wsl`!

## Validation Steps Performed

Manual validation for the following cases:

```c++
// MUST MANGLE
auto a01 = _tryMangleStartingDirectoryForWSL(LR"(wsl)", L"SENTINEL");
auto a02 = _tryMangleStartingDirectoryForWSL(LR"(wsl -d X)", L"SENTINEL");
auto a03 = _tryMangleStartingDirectoryForWSL(LR"(wsl -d X ~/bin/sh)", L"SENTINEL");
auto a04 = _tryMangleStartingDirectoryForWSL(LR"(wsl.exe)", L"SENTINEL");
auto a05 = _tryMangleStartingDirectoryForWSL(LR"(wsl.exe -d X)", L"SENTINEL");
auto a06 = _tryMangleStartingDirectoryForWSL(LR"(wsl.exe -d X ~/bin/sh)", L"SENTINEL");
auto a07 = _tryMangleStartingDirectoryForWSL(LR"("wsl")", L"SENTINEL");
auto a08 = _tryMangleStartingDirectoryForWSL(LR"("wsl.exe")", L"SENTINEL");
auto a09 = _tryMangleStartingDirectoryForWSL(LR"("wsl" -d X)", L"SENTINEL");
auto a10 = _tryMangleStartingDirectoryForWSL(LR"("wsl.exe" -d X)", L"SENTINEL");
auto a11 = _tryMangleStartingDirectoryForWSL(LR"("C:\Windows\system32\wsl.exe" -d X)", L"SENTINEL");
auto a12 = _tryMangleStartingDirectoryForWSL(LR"("C:\windows\system32\wsl" -d X)", L"SENTINEL");
auto a13 = _tryMangleStartingDirectoryForWSL(LR"(wsl ~/bin)", L"SENTINEL");

// MUST NOT MANGLE
auto a14 = _tryMangleStartingDirectoryForWSL(LR"("C:\wsl.exe" -d X)", L"SENTINEL");
auto a15 = _tryMangleStartingDirectoryForWSL(LR"(C:\wsl.exe)", L"SENTINEL");
auto a16 = _tryMangleStartingDirectoryForWSL(LR"(wsl --cd C:\)", L"SENTINEL");
auto a17 = _tryMangleStartingDirectoryForWSL(LR"(wsl ~)", L"SENTINEL");
auto a18 = _tryMangleStartingDirectoryForWSL(LR"(wsl ~ -d Ubuntu)", L"SENTINEL");
```

We don't have anywhere to put TerminalConnection unit tests :|

Closes #592.
2021-08-02 20:39:11 +00:00
James Holderness
6936ee15fe Make the alt buffer inherit cursor state from the main buffer (#10843)
When switching to the alt buffer, the starting cursor position, style,
and visibility is meant to be inherited from the main buffer. Similarly,
when returning to the main buffer, any changes made to those attributes
should be copied back (with the exception of the cursor position, which
is restored to its original state). This PR makes sure we handle that
cursor state correctly.

At some point I'd like to move the cursor state out of the
`SCREEN_INFORMATION` class, which would make this inheritance problem a
non-issue. For now, though, I've just made it copy the state from the
main buffer when creating the alt buffer, and copy it back when
returning to the main buffer.

## Validation Steps Performed

I've added some unit tests to verify the cursor state is inherited
correctly when switching to the alt buffer and back again. I also had to
make a small change to one of the existing alt buffer test that relied
on the initial cursor position being at 0;0, which is no longer the
case.

I've verified that the test case in issue #3545 is now working
correctly. I've also confirmed that this fixes a problem in the
_notcurses_ demo, where the cursor was showing when it should have been
hidden.

Closes #3545
2021-08-02 19:56:12 +00:00
Mike Griese
a151607c79 Recalculate quake window size when snapping across monitors (#10744)
## Summary of the Pull Request

<kbd>win+shift+arrows</kbd> can be used to move windows to adjacent monitors. When that happens, we'll new re-calculate the size of the window for the new monitor.

## References
* megathread: #8888

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

## Detailed Description of the Pull Request / Additional comments

In `WM_WINDOWPOSCHANGING`, the OS says "hey, I'm about to do {something} to your window. You cool with that?". We handle that message by:
1. checking if the window was _moved_ as a part of this message
2. getting the monitor that the window will be moved onto
3. If that monitor is different than the monitor the window is currently on, then
  * calculate how big the quake window should be on that monitor
  * tell the OS that's where we'd like to be.

## Validation Steps Performed

* <kbd>win+shift+arrows</kbd> works right now
* normal quake summoning still works right
2021-08-02 19:42:57 +00:00
Leonard Hecker
fc64ff3029 Vectorize TextColor::GetColor (#10779)
I was watching a video about vectorized instructions and I wanted to
try out some new things, as I had never written AVX code before.
This commit is the result of this tiny Thursday morning detour into
AVX land. It improves performance of `TextColor::GetColor` by about 3x.

## Validation Steps Performed

* Default colors are still properly shifted +8 ✔️
2021-08-02 19:02:59 +00:00
Ian O'Neill
34a6b1913c Set drag and drop on '+' tooltip text based on keyboard modifiers (#10841)
Sets the tooltip text on the '+' button based on the keyboard modifiers
when dragging and dropping.

## Validation Steps Performed
Manually tested - dragged a directory onto the '+ button and saw that
* The text changed when `shift` was pressed
* The text changed when `alt` was pressed
* The text changed back when `shift` or `alt` were released

Closes #10722
2021-08-02 18:44:39 +00:00
Mike Griese
4b45bb8df1 Fix a pair of TermControl dragging bugs (#10650)
## Summary of the Pull Request

This fixes two bugs related to dragging into the bounds of the `TermControl`. Although the fixes are fairly small, I'm batching them up, because I don't want to stack 2 more PRs on top of #10051.

* #9109 
  - This is fixed by only starting an autoscroll if the click&drag actually started within the bounds of the control. 
* #4603
  - Building on the above change, only modify the selection when the drag started in the control. 
 
## References
* srsly go read #10051.

## PR Checklist
* [x] Closes #9109
* [x] Closes #4603
* [x] I work here
* [x] Test added
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

This is kind of annoying that the auto-scrolling is handled by the TermControl, but it uses a timer that's still a WinUI construct.

We only want to start the auto-scrolling behavior when the drag started _inside_ the control. Otherwise, in the tab drag scenario, dragging into the bounds of the TermControl will trick it into thinking it should start a scroll.
2021-07-28 22:27:09 +00:00
Mike Griese
f058b08fde Account for the window borders when restoring from fullscreen (#10737)
## Summary of the Pull Request

When we're restoring from fullscreen, we do a little adjustment to make sure to clamp the window bounds within the bounds of the active monitor. We unfortunately didn't account for the size of the non-client area (the invisible borders around our 1px border). This didn't matter most of the time, but if the window was within ~8px of the side of the monitor (any side), then restoring from fullscreen would actually move it to the wrong place. 

As it turns out, the `_quake` window is within ~8px of the edges of the monitor _very often_.

## References
* regressed in #9737

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

## Validation Steps Performed
The repro in the bug was fairly straightforward. It doesn't happen anymore.
2021-07-28 22:18:58 +00:00
Mike Griese
b1bcc59230 Shift the island up by 1px when maximized (#10746)
For inexplicable reasons, the top row of pixels on our tabs, new tab
button, and caption buttons is totally unclickable. The mouse simply
refuses to interact with them. So when we're maximized, on certain
monitor configurations, this results in the top row of pixels not
reacting to clicks at all.

To obey Fitt's Law, we're gonna hackily shift the entire island up one
pixel. That will result in the top row of pixels in the window actually
being the _second_ row of pixels for those buttons, which will make them
clickable. It's perhaps not the right fix, but it works.

After discussion, we think this is a fine fix for this. We don't think
anyone's going to miss the top row of pixels on the TabView. The original
bug is painful enough for the subset of users it impacts that this is an
acceptable trade. Should a better fix be found, we can absolutely do that
instead.

Closes #7422
2021-07-28 22:15:22 +00:00
Floris Westerman
10222a2ba2 Passing through moveFocus keys when moving to another pane failed (#10806)
<!-- 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
Implementation of #6219 with a small tweak, not just passing the keys when no panes are present, but passing on the keys when there is no other pane to move to. This enables another usecase: 2 panes in terminal split vertically; in one of these panes running tmux with two panes that are split horizontally. This allows the user to still navigate between tmux panes even though they have terminal panes open.

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

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #6219
* [x] CLA signed.
* [x] Tests added/passed
* [x] Documentation updated. I don't think that's necessary
* [x] Schema updated. N/A
* [ ] 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.

<!-- 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
Implementation by propagating the boolean indicating success of moving focus all the way to the action handler, where this result will determine whether the action will be considered handled or not. When the action is not handled, the keychord will be propagated to the terminal.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manual testing; all relevant unit tests still work
2021-07-28 22:05:32 +00:00
Floris Westerman
3f5f37d910 Fix: Multimedia Key Hotkey Support (#10801)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Fixes/implements #10058 according to directions in that issue: added support for browser navigation keys to be used in actions.

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

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

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

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

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

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

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

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

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

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

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

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

## Validation Steps Performed

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

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

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

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

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

## Detailed Description

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

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

## Validation Steps Performed
It works!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Validation Steps Performed

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

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

## Validation Steps Performed

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

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

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

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

## References
* megathread: #8888

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

## Validation Steps Performed

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

## PR Checklist

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

## Validation Steps Performed

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

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

## Validation Steps Performed

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

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

## Summary of the Pull Request

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

Why?

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

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

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

## Validation Steps Performed

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

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

## Detailed Description of the Pull Request / Additional comments

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

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

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

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

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

Burned in for 1k runs-

BEFORE

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

AFTER

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

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

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

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

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

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

## Validation Steps Performed

The terminal behaves basically the same as before.

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

## References
#6900 - Settings UI Epic

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

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

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

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

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

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

## Validation Steps Performed

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

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

## Validation Steps Performed

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

References #8613
Closes #10658
2021-07-14 16:46:34 +00:00
PankajBhojwani
d13c37cd60 Allow creating and editing unfocused appearances in the SUI (#10317)
## Summary of the Pull Request
Adds unfocused appearance creation/configuration in the SUI

There is now an 'Unfocused Appearance' section at the bottom of the 'Appearance' tab in a profile. There is a '+' button to create an unfocused appearance if one does not exist, or a delete button to delete the unfocused appearance if one exists (only one of these buttons is visible at a time). 

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

## Validation Steps Performed
![unfocusedSUI](https://user-images.githubusercontent.com/26824113/125523613-48aefe28-b4cf-46a2-91c9-2ba3ea89e071.gif)
2021-07-13 23:33:22 +00:00
Leonard Hecker
32fbd4cbb6 Enable /Zc:preprocessor (#10593)
This commit is a preparation for upcoming changes to KeyChordSerialization for #7539 and #10203.
In order to support variadic macros, /Zc:preprocessor was enabled, which required changing unrelated parts of the project.

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

## Validation Steps Performed

* Project still compiles ✔️
2021-07-13 23:00:11 +00:00
Josh Soref
6d7723e3be Upgrade check-spelling to v0.0.19 (#10646)
Updates check-spelling to 0.0.19

https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p

I'm pinning `actions/checkout` to @v2 instead of micromanaging the
version. We have reasonable faith that GitHub will do a good job of
maintaining their version branch.

I'll probably introduce a version branch for check-spelling in the near
future as well.  The job name change is for future bits -- I originally
copied the name from a template and didn't understand its significance
-- eventually it'll actually be used by the workflow. And if one uses
`act`, having distinct / well named jobs is actually useful.
2021-07-13 11:21:44 -05:00
Dan Thompson
e2005ca5d7 Merged PR 6176782: [Git2Git] Get rid of dead build macros/#defines FE_IME, W32_SB, etc.
Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev 40df712b33a40e76aeeac87f823bbb028d2e3972

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

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

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

View File

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

View File

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

View File

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

View File

@@ -7,3 +7,4 @@ Iosevka
MDL
Monofur
Segoe
wght

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
name: Spell checking
on:
pull_request_target:
push:
jobs:
build:
name: Spell checking
runs-on: ubuntu-latest
steps:
- name: checkout-merge
if: "contains(github.event_name, 'pull_request')"
uses: actions/checkout@v2.0.0
with:
ref: refs/pull/${{github.event.pull_request.number}}/merge
- name: checkout
if: "!contains(github.event_name, 'pull_request')"
uses: actions/checkout@v2.0.0
- uses: check-spelling/check-spelling@v0.0.18

134
.github/workflows/spelling2.yml vendored Normal file
View File

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

View File

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

View File

@@ -21,7 +21,7 @@ Write-Host "Checking test results..."
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri -IncludeRunDetails
Write-Host "queryUri = $queryUri"
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
$testRuns = Invoke-RestMethodWithRetries $queryUri -Headers $azureDevOpsRestApiHeaders
[System.Collections.Generic.List[string]]$failingTests = @()
[System.Collections.Generic.List[string]]$unreliableTests = @()
[System.Collections.Generic.List[string]]$unexpectedResultTest = @()
@@ -50,7 +50,7 @@ foreach ($testRun in ($testRuns.value | Sort-Object -Property "completedDate" -D
$totalTestsExecutedCount += $testRun.totalTests
$testRunResultsUri = "$($testRun.url)/results?api-version=5.0"
$testResults = Invoke-RestMethod -Uri "$($testRun.url)/results?api-version=5.0" -Method Get -Headers $azureDevOpsRestApiHeaders
$testResults = Invoke-RestMethodWithRetries "$($testRun.url)/results?api-version=5.0" -Headers $azureDevOpsRestApiHeaders
foreach ($testResult in $testResults.value)
{

View File

@@ -9,7 +9,7 @@ $payloadDir = "HelixPayload\$Configuration\$Platform"
$repoDirectory = Join-Path (Split-Path -Parent $script:MyInvocation.MyCommand.Path) "..\..\"
$nugetPackagesDir = Join-Path (Split-Path -Parent $script:MyInvocation.MyCommand.Path) "packages"
# Create the payload directory. Remove it if it already exists.
If(test-path $payloadDir)
{
@@ -19,8 +19,8 @@ New-Item -ItemType Directory -Force -Path $payloadDir
# Copy files from nuget packages
Copy-Item "$nugetPackagesDir\microsoft.windows.apps.test.1.0.181203002\lib\netcoreapp2.1\*.dll" $payloadDir
Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.58.210305002\build\Binaries\$Platform\*" $payloadDir
Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.58.210305002\build\Binaries\$Platform\NetFx4.5\*" $payloadDir
Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.60.210621002\build\Binaries\$Platform\*" $payloadDir
Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.60.210621002\build\Binaries\$Platform\NetFx4.5\*" $payloadDir
New-Item -ItemType Directory -Force -Path "$payloadDir\.NETCoreApp2.1\"
Copy-Item "$nugetPackagesDir\runtime.win-$Platform.microsoft.netcore.app.2.1.0\runtimes\win-$Platform\lib\netcoreapp2.1\*" "$payloadDir\.NETCoreApp2.1\"
Copy-Item "$nugetPackagesDir\runtime.win-$Platform.microsoft.netcore.app.2.1.0\runtimes\win-$Platform\native\*" "$payloadDir\.NETCoreApp2.1\"
@@ -59,7 +59,7 @@ Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"
Copy-Item "$repoDirectory\Artifacts\$ArtifactName\appx\CascadiaPackage_0.0.1.0_$Platform.msix" $payloadDir\CascadiaPackage.zip
# Rename it to extension of ZIP because Expand-Archive is real sassy on the build machines
# and refuses to unzip it because of its file extension while on a desktop, it just
# and refuses to unzip it because of its file extension while on a desktop, it just
# does the job without complaining.
# Extract the APPX package

View File

@@ -20,13 +20,31 @@ function Generate-File-Links
Out-File -FilePath $helixLinkFile -Append -InputObject "<ul>"
foreach($file in $files)
{
Out-File -FilePath $helixLinkFile -Append -InputObject "<li><a href=$($file.Link)>$($file.Name)</a></li>"
$url = Append-HelixAccessTokenToUrl $file.Link "{Your-Helix-Access-Token-Here}"
Out-File -FilePath $helixLinkFile -Append -InputObject "<li>$($url)</li>"
}
Out-File -FilePath $helixLinkFile -Append -InputObject "</ul>"
Out-File -FilePath $helixLinkFile -Append -InputObject "</div>"
}
}
function Append-HelixAccessTokenToUrl
{
Param ([string]$url, [string]$token)
if($token)
{
if($url.Contains("?"))
{
$url = "$($url)&access_token=$($token)"
}
else
{
$url = "$($url)?access_token=$($token)"
}
}
return $url
}
#Create output directory
New-Item $OutputFolder -ItemType Directory
@@ -63,7 +81,8 @@ foreach ($testRun in $testRuns.value)
if (-not $workItems.Contains($workItem))
{
$workItems.Add($workItem)
$filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files$accessTokenParam"
$filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files"
$filesQueryUri = Append-HelixAccessTokenToUrl $filesQueryUri $helixAccessToken
$files = Invoke-RestMethodWithRetries $filesQueryUri
$screenShots = $files | where { $_.Name.EndsWith(".jpg") }
@@ -102,6 +121,7 @@ foreach ($testRun in $testRuns.value)
Write-Host "Downloading $link to $destination"
$link = Append-HelixAccessTokenToUrl $link $HelixAccessToken
Download-FileWithRetries $link $destination
}
}

View File

@@ -23,7 +23,7 @@ Write-Host "queryUri = $queryUri"
# To account for unreliable tests, we'll iterate through all of the tests associated with this build, check to see any tests that were unreliable
# (denoted by being marked as "skipped"), and if so, we'll instead mark those tests with a warning and enumerate all of the attempted runs
# with their pass/fail states as well as any relevant error messages for failed attempts.
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
$testRuns = Invoke-RestMethodWithRetries $queryUri -Headers $azureDevOpsRestApiHeaders
$timesSeenByRunName = @{}
@@ -32,10 +32,10 @@ foreach ($testRun in $testRuns.value)
$testRunResultsUri = "$($testRun.url)/results?api-version=5.0"
Write-Host "Marking test run `"$($testRun.name)`" as in progress so we can change its results to account for unreliable tests."
Invoke-RestMethod -Uri "$($testRun.url)?api-version=5.0" -Method Patch -Body (ConvertTo-Json @{ "state" = "InProgress" }) -Headers $azureDevOpsRestApiHeaders -ContentType "application/json" | Out-Null
Invoke-RestMethod "$($testRun.url)?api-version=5.0" -Method Patch -Body (ConvertTo-Json @{ "state" = "InProgress" }) -Headers $azureDevOpsRestApiHeaders -ContentType "application/json" | Out-Null
Write-Host "Retrieving test results..."
$testResults = Invoke-RestMethod -Uri $testRunResultsUri -Method Get -Headers $azureDevOpsRestApiHeaders
$testResults = Invoke-RestMethodWithRetries $testRunResultsUri -Headers $azureDevOpsRestApiHeaders
foreach ($testResult in $testResults.value)
{
@@ -54,7 +54,8 @@ foreach ($testRun in $testRuns.value)
Write-Host " Test $($testResult.testCaseTitle) was detected as unreliable. Updating..."
# The errorMessage field contains a link to the JSON-encoded rerun result data.
$rerunResults = ConvertFrom-Json (New-Object System.Net.WebClient).DownloadString($testResult.errorMessage)
$resultsJson = Download-StringWithRetries "Error results" $testResult.errorMessage
$rerunResults = ConvertFrom-Json $resultsJson
[System.Collections.Generic.List[System.Collections.Hashtable]]$rerunDataList = @()
$attemptCount = 0
$passCount = 0

View File

@@ -2,7 +2,7 @@
<packages>
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
<package id="Microsoft.Internal.Windows.Terminal.TestContent" version="1.0.1" />
<package id="Microsoft.Taef" version="10.58.210305002" targetFramework="native" />
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
<package id="microsoft.windows.apps.test" version="1.0.181203002" targetFramework="native" />
<package id="runtime.win-x86.microsoft.netcore.app" version="2.1.0" targetFramework="native" />
<package id="runtime.win-x64.microsoft.netcore.app" version="2.1.0" targetFramework="native" />

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
<package id="Microsoft.Taef" version="10.58.210305002" targetFramework="native" />
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
</packages>

View File

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

View File

@@ -8,9 +8,12 @@ jobs:
variables:
BuildConfiguration: AuditMode
BuildPlatform: ${{ parameters.platform }}
pool: "windevbuildagents"
# The public pool is also an option!
# pool: { vmImage: windows-2019 }
pool:
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
steps:
- checkout: self

View File

@@ -11,9 +11,12 @@ jobs:
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
pool: "windevbuildagents"
# The public pool is also an option!
# pool: { vmImage: windows-2019 }
pool:
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
steps:
- template: build-console-steps.yml

View File

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

View File

@@ -3,7 +3,7 @@ parameters:
platform: ''
additionalBuildArguments: ''
minimumExpectedTestsExecutedCount: 1 # Sanity check for minimum expected tests to be reported
rerunPassesRequiredToAvoidFailure: 0
rerunPassesRequiredToAvoidFailure: 5
jobs:
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
@@ -12,9 +12,12 @@ jobs:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
PGOBuildMode: 'Instrument'
pool: "windevbuildagents"
# The public pool is also an option!
# pool: { vmImage: windows-2019 }
pool:
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
steps:
- template: build-console-steps.yml

View File

@@ -11,7 +11,7 @@ jobs:
clean: true
- task: PowerShell@2
displayName: 'Code Formattting Check'
displayName: 'Code Formatting Check'
inputs:
targetType: filePath
filePath: '.\build\scripts\Invoke-FormattingCheck.ps1'

View File

@@ -12,4 +12,4 @@ steps:
inputs:
targetType: filePath
filePath: build\Helix\GenerateTestProjFile.ps1
arguments: -TestFile '${{ parameters.testFilePath }}' -OutputProjFile '$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\$(BuildPlatform)\${{ parameters.outputProjFileName }}' -JobTestSuiteName '${{ parameters.testSuite }}' -TaefPath '$(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.58.210305002\build\Binaries\x86' -TaefQuery '${{ parameters.taefQuery }}'
arguments: -TestFile '${{ parameters.testFilePath }}' -OutputProjFile '$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\$(BuildPlatform)\${{ parameters.outputProjFileName }}' -JobTestSuiteName '${{ parameters.testSuite }}' -TaefPath '$(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.60.210621002\build\Binaries\x86' -TaefQuery '${{ parameters.taefQuery }}'

View File

@@ -22,6 +22,7 @@ jobs:
condition: succeededOrFailed()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
HelixAccessToken: $(HelixApiAccessToken)
inputs:
targetType: filePath
filePath: build\Helix\UpdateUnreliableTests.ps1
@@ -32,6 +33,7 @@ jobs:
condition: succeededOrFailed()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
HelixAccessToken: $(HelixApiAccessToken)
inputs:
targetType: filePath
filePath: build\Helix\OutputTestResults.ps1

View File

@@ -15,6 +15,7 @@ parameters:
# if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline:
useBuildOutputFromPipeline: $(System.DefinitionId)
openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml'
closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml'
jobs:
- job: ${{ parameters.name }}
@@ -29,11 +30,11 @@ jobs:
buildConfiguration: ${{ parameters.configuration }}
buildPlatform: ${{ parameters.platform }}
openHelixTargetQueues: ${{ parameters.openHelixTargetQueues }}
closedHelixTargetQueues: ${{ parameters.closedHelixTargetQueues }}
artifactsDir: $(Build.SourcesDirectory)\Artifacts
taefPath: $(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.58.210305002\build\Binaries\$(buildPlatform)
taefPath: $(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.60.210621002\build\Binaries\$(buildPlatform)
helixCommonArgs: '/binaryLogger:$(Build.SourcesDirectory)/${{parameters.name}}.$(buildPlatform).$(buildConfiguration).binlog /p:HelixBuild=$(Build.BuildId).$(buildPlatform).$(buildConfiguration) /p:Platform=$(buildPlatform) /p:Configuration=$(buildConfiguration) /p:HelixType=${{parameters.helixType}} /p:TestSuite=${{parameters.testSuite}} /p:ProjFilesPath=$(Build.ArtifactStagingDirectory) /p:rerunPassesRequiredToAvoidFailure=${{parameters.rerunPassesRequiredToAvoidFailure}}'
steps:
- task: CmdLine@1
displayName: 'Display build machine environment variables'
@@ -140,6 +141,7 @@ jobs:
- task: DotNetCoreCLI@2
displayName: 'Run tests in Helix (open queues)'
condition: and(succeeded(),eq(variables['System.CollectionUri'],'https://dev.azure.com/ms/'))
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
@@ -148,3 +150,14 @@ jobs:
custom: msbuild
arguments: '$(helixCommonArgs) /p:IsExternal=true /p:Creator=Terminal /p:HelixTargetQueues=$(openHelixTargetQueues)'
- task: DotNetCoreCLI@2
displayName: 'Run tests in Helix (closed queues)'
condition: and(succeeded(),ne(variables['System.CollectionUri'],'https://dev.azure.com/ms/'))
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
HelixAccessToken: $(HelixApiAccessToken)
inputs:
command: custom
projects: build\Helix\RunTestsInHelix.proj
custom: msbuild
arguments: '$(helixCommonArgs) /p:HelixTargetQueues=$(closedHelixTargetQueues)'

View File

@@ -20,11 +20,15 @@ jobs:
inputs:
artifactName: ${{ parameters.pgoArtifact }}
downloadPath: $(artifactsPath)
- task: NuGetToolInstaller@0
displayName: 'Use NuGet 5.2.0'
- task: NuGetAuthenticate@0
inputs:
versionSpec: 5.2.0
nuGetServiceConnections: 'Terminal Public Artifact Feed'
- task: NuGetToolInstaller@0
displayName: 'Use NuGet 5.8.0'
inputs:
versionSpec: 5.8.0
- task: CopyFiles@2
displayName: 'Copy pgd files to NuGet build directory'
@@ -58,5 +62,11 @@ jobs:
displayName: 'NuGet push'
inputs:
command: push
publishVstsFeed: Terminal/TerminalDependencies
packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg
nuGetFeedType: external
packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg
# The actual URL and PAT for this feed is configured at
# https://microsoft.visualstudio.com/Dart/_settings/adminservices
# This is the name of that connection
publishFeedCredentials: 'Terminal Public Artifact Feed'
feedsToUse: config
nugetConfigPath: '$(Build.SourcesDirectory)/NuGet.config'

View File

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

View File

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

View File

@@ -4,13 +4,13 @@
"title": "Microsoft's Windows Terminal Settings Profile Schema",
"definitions": {
"KeyChordSegment": {
"pattern": "^(?<modifier>(?<mod1>ctrl|alt|shift|win)(?:\\+(?<mod2>ctrl|alt|shift|win)(?<!\\k<mod1>))?(?:\\+(?<mod3>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>))?(?:\\+(?<mod4>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>|\\k<mod3>))?\\+)?(?<key>[^\\s+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
"pattern": "^(?:(?:ctrl|alt|shift|win)\\+)*(?:app|backspace|browser_(?:back|forward|refresh|stop|search|favorites|home)|comma|delete|down|end|enter|esc|escape|home|insert|left|menu|minus|pagedown|pageup|period|pgdn|pgup|plus|right|space|tab|up|f(?:1\\d?|2[0-4]?|[3-9])|numpad\\d|numpad_(?:\\d|add|decimal|divide|minus|multiply|period|plus|subtract)|(?:vk|sc)\\((?:[1-9]|1?\\d{2}|2[0-4]\\d|25[0-5])\\)|[^\\s+])(?:\\+(?:ctrl|alt|shift|win))*$",
"type": "string",
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<keyName>\", where each modifier is optional, separated by + symbols, and keyName is either one of the names listed in the table below, or any single key character. The string should be written in full lowercase.\napp, menu\tMENU key\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<KeyName>\", where each modifier is optional. KeyName is either any single key character, an explicit virtual key or scan code in the form vk(nnn) and sc(nnn) respectively, or one of the special names listed at https://docs.microsoft.com/en-us/windows/terminal/customize-settings/actions#accepted-modifiers-and-keys"
},
"Color": {
"default": "#",
"pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
"pattern": "^#[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{3})?$",
"type": "string",
"format": "color"
},
@@ -154,6 +154,17 @@
"description": "Sets how the background image aligns to the boundaries of the window when unfocused. Possible values: \"center\", \"left\", \"top\", \"right\", \"bottom\", \"topLeft\", \"topRight\", \"bottomLeft\", \"bottomRight\"",
"type": "string"
},
"intenseTextStyle": {
"default": "bright",
"description": "Controls how 'intense' text is rendered. Values are \"bold\", \"bright\", \"all\" and \"none\"",
"enum": [
"none",
"bold",
"bright",
"all"
],
"type": "string"
},
"experimental.retroTerminalEffect": {
"description": "When set to true, enable retro terminal effects when unfocused. This is an experimental feature, and its continued existence is not guaranteed.",
"type": "boolean"
@@ -204,6 +215,22 @@
"type": "integer"
}
]
},
"features": {
"description": "Sets the DWrite font features for the given font. For example, { \"ss01\": 1, \"liga\":0 } will enable ss01 and disable ligatures.",
"type": "object",
"patternProperties": {
"^(([A-Za-z0-9]){4})$": { "type": "integer" }
},
"additionalProperties": false
},
"axes": {
"description": "Sets the DWrite font axes for the given font. For example, { \"wght\": 200 } will set the font weight to 200.",
"type": "object",
"patternProperties": {
"^([A-Za-z]{4})$": { "type": "number" }
},
"additionalProperties": false
}
},
"type": "object"
@@ -238,7 +265,10 @@
"identifyWindow",
"identifyWindows",
"moveFocus",
"movePane",
"swapPane",
"moveTab",
"multipleActions",
"newTab",
"newWindow",
"nextTab",
@@ -249,6 +279,7 @@
"paste",
"prevTab",
"renameTab",
"openSystemMenu",
"openTabRenamer",
"quakeMode",
"resetFontSize",
@@ -270,9 +301,11 @@
"toggleFocusMode",
"toggleFullscreen",
"togglePaneZoom",
"toggleSplitOrientation",
"toggleReadOnlyMode",
"toggleShaderEffects",
"wt",
"quit",
"unbound"
],
"type": "string"
@@ -283,7 +316,10 @@
"right",
"up",
"down",
"previous"
"previous",
"nextInOrder",
"previousInOrder",
"first"
],
"type": "string"
},
@@ -491,6 +527,23 @@
"type": "integer",
"default": 0,
"description": "Which tab to switch to, with the first being 0"
}
}
}
],
"required": [ "index" ]
},
"MovePaneAction": {
"description": "Arguments corresponding to a Move Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "movePane" },
"index": {
"type": "integer",
"default": 0,
"description": "Which tab to move the pane to, with the first being 0"
}
}
}
@@ -507,7 +560,24 @@
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
"description": "The direction to move focus in, between panes. Direction can be 'previous' to move to the most recently used pane."
"description": "The direction to move focus in, between panes. Direction can be 'previous' to move to the most recently used pane, or 'nextInOrder' or 'previousInOrder' to move to the next or previous pane."
}
}
}
],
"required": [ "direction" ]
},
"SwapPaneAction": {
"description": "Arguments corresponding to a Swap Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "swapPane" },
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
"description": "The direction to move the focus pane in, swapping panes. Direction can be 'previous' to swap with the most recently used pane, or 'nextInOrder' or 'previousInOrder' to move to the next or previous pane."
}
}
}
@@ -596,7 +666,7 @@
"defaultsFile",
"allFiles",
"settingsUI"
]
}
}
@@ -758,6 +828,24 @@
],
"required": [ "direction" ]
},
"MultipleActionsAction": {
"description": "Arguments for the multiple actions command",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "multipleActions" },
"actions" : {
"$ref": "#/definitions/ShortcutAction",
"type": "array",
"minItems": 1,
"description": "A list of other actions."
}
}
}
],
"required": [ "actions" ]
},
"CommandPaletteAction": {
"description": "Arguments for a commandPalette action",
"allOf": [
@@ -952,6 +1040,8 @@
{ "$ref": "#/definitions/NewTabAction" },
{ "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/MovePaneAction" },
{ "$ref": "#/definitions/SwapPaneAction" },
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SendInputAction" },
{ "$ref": "#/definitions/SplitPaneAction" },
@@ -1116,6 +1206,10 @@
"description": "When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one.",
"type": "boolean"
},
"experimental.input.forceVT": {
"description": "Force the terminal to use the legacy input encoding. Certain keys in some applications may stop working when enabling this setting.",
"type": "boolean"
},
"initialCols": {
"default": 120,
"description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",
@@ -1139,6 +1233,15 @@
"description": "When set to true, this enables the launch of Windows Terminal at startup. Setting this to false will disable the startup task entry. If the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect.",
"type": "boolean"
},
"firstWindowPreference": {
"default": "defaultProfile",
"description": "Defines what behavior the terminal takes when it starts. \"defaultProfile\" will have the terminal launch with one tab of the default profile, and \"persistedWindowLayout\" will cause the terminal to save its layout on close and reload it on open.",
"enum": [
"defaultProfile",
"persistedWindowLayout"
],
"type": "string"
},
"launchMode": {
"default": "default",
"description": "Defines whether the terminal will launch as maximized, full screen, or in a window. Setting this to \"focus\" is equivalent to launching the terminal in the \"default\" mode, but with the focus mode enabled. Similar, setting this to \"maximizedFocus\" will result in launching the terminal in a maximized window with the focus mode enabled.",
@@ -1158,6 +1261,21 @@
"minimum": 0,
"type": [ "integer", "string" ],
"deprecated": true
},
"minimizeToTray": {
"default": "false",
"description": "When set to true, minimizing a Terminal window will no longer appear in the taskbar. Instead, a Terminal icon will appear in the system tray through which the user can access their windows.",
"type": "boolean"
},
"alwaysShowTrayIcon": {
"default": "false",
"description": "When set to true, the Terminal's tray icon will always be shown in the system tray.",
"type": "boolean"
},
"useAcrylicInTabRow": {
"default": "false",
"description": "When set to true, the tab row will have an acrylic background with 50% opacity.",
"type": "boolean"
},
"actions": {
"description": "Properties are specific to each custom action.",

View File

@@ -0,0 +1,619 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-11-20
last updated: 2021-08-17
issue id: #1032
---
# Elevation Quality of Life Improvements
## Abstract
For a long time, we've been researching adding support to the Windows Terminal
for running both unelevated and elevated (admin) tabs side-by-side, in the same
window. However, after much research, we've determined that there isn't a safe
way to do this without opening the Terminal up as a potential
escalation-of-privilege vector.
Instead, we'll be adding a number of features to the Terminal to improve the
user experience of working in elevated scenarios. These improvements include:
* A visible indicator that the Terminal window is elevated ([#1939])
* Configuring the Terminal to always run elevated ([#632])
* Configuring a specific profile to always open elevated ([#632])
* Allowing new tabs, panes to be opened elevated directly from an unelevated
window
* Dynamic profile appearance that changes depending on if the Terminal is
elevated or not. ([#1939], [#8311])
## Background
_This section was originally authored in the [Process Model 2.0 Spec]. Please
refer to it there for its original context._
Let's presume that you're a user who wants to be able to open an elevated tab
within an otherwise unelevated Terminal window. We call this scenario "mixed
elevation" - the tabs within the Terminal can be running either unelevated _or_
elevated client applications.
It wouldn't be terribly difficult for the unelevated Terminal to request the
permission of the user to spawn an elevated client application. The user would
see a UAC prompt, they'd accept, and then they'd be able to have an elevated
shell alongside their unelevated tabs.
However, this creates an escalation of privilege vector. Now, there's an
unelevated window which is connected directly to an elevated process. At this
point, **any other unelevated application could send input to the Terminal's
`HWND`**. This would make it possible for another unelevated process to "drive"
the Terminal window, and send commands to the elevated client application.
It was initially theorized that the window/content model architecture would also
help enable "mixed elevation". With mixed elevation, tabs could run at different
integrity levels within the same terminal window. However, after investigation
and research, it has become apparent that this scenario is not possible to do
safely after all. There are numerous technical difficulties involved, and each
with their own security risks. At the end of the day, the team wouldn't be
comfortable shipping a mixed-elevation solution, because there's simply no way
for us to be confident that we haven't introduced an escalation-of-privilege
vector utilizing the Terminal. No matter how small the attack surface might be,
we wouldn't be confident that there are _no_ vectors for an attack.
Some things we considered during this investigation:
* If a user requests a new elevated tab from an otherwise unelevated window, we
could use UAC to create a new, elevated window process, and "move" all the
current tabs to that window process, as well as the new elevated client. Now,
the window process would be elevated, preventing it from input injection, and
it would still contains all the previously existing tabs. The original window
process could now be discarded, as the new elevated window process will
pretend to be the original window.
- However, it is unfortunately not possible with COM to have an elevated
client attach to an unelevated server that's registered at runtime. Even in
a packaged environment, the OS will reject the attempt to `CoCreateInstance`
the content process object. this will prevent elevated windows from
re-connecting to unelevated client processes.
- We could theoretically build an RPC tunnel between content and window
processes, and use the RPC connection to marshal the content process to the
elevated window. However, then _we_ would need to be responsible for
securing access the the RPC endpoint, and we feel even less confident doing
that.
- Attempts were also made to use a window-broker-content architecture, with
the broker process having a static CLSID in the registry, and having the
window and content processes at mixed elevation levels `CoCreateInstance`
that broker. This however _also_ did not work across elevation levels. This
may be due to a lack of Packaged COM support for mixed elevation levels.
It's also possible that the author forgot that packaged WinRT doesn't play
nicely with creating objects in an elevated context. The Terminal has
previously needed to manually manifest all its classes in a SxS manifest for
Unpackaged WinRT to allow the classes to be activated, rather than relying
on the packaged catalog. It's theoretically possible that doing that would
have allowed the broker to be activated across integrity levels.
Even if this approach did end up working, we would still need to be
responsible for securing the elevated windows so that an unelevated attacker
couldn't hijack a content process and trigger unexpected code in the window
process. We didn't feel confident that we could properly secure this channel
either.
We also considered allowing mixed content in windows that were _originally_
elevated. If the window is already elevated, then it can launch new unelevated
processes. We could allow elevated windows to still create unelevated
connections. However, we'd want to indicate per-pane what the elevation state
of each connection is. The user would then need to keep track themselves of
which terminal instances are elevated, and which are not.
This also marks a departure from the current behavior, where everything in an
elevated window would be elevated by default. The user would need to specify for
each thing in the elevated window that they'd want to create it elevated. Or the
Terminal would need to provide some setting like
`"autoElevateEverythingInAnElevatedWindow"`.
We cannot support mixed elevation when starting in a unelevated window.
Therefore, it doesn't make a lot of UX sense to support it in the other
direction. It's a cleaner UX story to just have everything in a single window at
the same elevation level.
## Solution Design
Instead of supporting mixed elevation in the same window, we'll introduce the
following features to the Terminal. These are meant as a way of improving the
quality of life for users who work in mixed-elevation (or even just elevated)
environments.
### Visible indicator for elevated windows
As requested in [#1939], it would be nice if it was easy to visibly identify if
a Terminal window was elevated or not.
One easy way of doing this is by adding a simple UAC shield to the left of the
tabs for elevated windows. This shield could be configured by the theme (see
[#3327]). We could provide the following states:
* Colored (the default)
* Monochrome
* Hidden, to hide the shield even on elevated windows. This is the current
behavior.
![UAC-shield-in-titlebar](UAC-shield-in-titlebar.png)
_figure 1: a monochrome UAC shield in the titlebar of the window, courtesy of @mdtauk_
We could also simplify this to only allow a boolean true/false for displaying
the shield. As we do often with other enums, we could define `true` to be the
same as the default appearance, and `false` to be the hidden option. As always,
the development of the Terminal is an iterative process, where we can
incrementally improve from no setting, to a boolean setting, to a enum-backed
one.
### Configuring a profile to always run elevated
Oftentimes, users might have a particular tool chain that only works when
running elevated. In these scenarios, it would be convenient for the user to be
able to identify that the profile should _always_ run elevated. That way, they
could open the profile from the dropdown menu of an otherwise unelevated window
and have the elevated window open with the profile automatically.
We'll be adding the `"elevate": true|false` setting as a per-profile setting,
with a default value of `false`. When set to `true`, we'll try to auto-elevate
the profile whenever it's launched. We'll check to see if this window is
elevated before creating the connection for this profile. If the window is not
elevated, then we'll create a new window with the requested elevation level to
handle the new connection.
`"elevate": false` will do nothing. If the window is already elevated, then the
profile won't open an un-elevated window.
If the user tries to open an `"elevate": true` profile in a window that's
already elevated, then a new tab/split will open in the existing window, rather
than spawning an additional elevated window.
There are three situations where we're creating new terminal instances: new
tabs, new splits, and new windows. Currently, these are all actions that are
also exposed in the `wt` commandline as subcommands. We can convert from the
commandline arguments into these actions already. Therefore, it shouldn't be too
challenging to convert these actions back into the equal commandline arguments.
For the following examples, let's assume the user is currently in an unelevated
Terminal window.
When the user tries to create a new elevated **tab**, we'll need to create a new
process, elevated, with the following commandline:
```
wt new-tab [args...]
```
When we create this new `wt` instance, it will obey the glomming rules as
specified in [Session Management Spec]. It might end up glomming to another
existing window at that elevation level, or possibly create its own window.
Similarly, for a new elevated **window**, we can make sure to pass the `-w new`
arguments to `wt`. These parameters indicate that we definitely want this
command to run in a new window, regardless of the current glomming settings.
```
wt -w new new-tab [args...]
```
However, creating a new **pane** is a little trickier. Invoking the `wt
split-pane [args...]` is straightforward enough.
<!-- Discussion notes follow:
If the current window doesn't have the same elevation level as the
requested profile, do we always want to just create a new split? If the command
ends up glomming to an existing window, does that even make sense? That invoking
an elevated split in an unelevated window would end up splitting the elevated
window? It's very possible that the user wanted a split in the tab they're
currently in, in the unelevated window, but they don't want a split in the
elevated window.
What if there's not space in the elevated window to create the split (but there
would be in the current window)? That would sure make it seem like nothing
happened, silently.
We could alternatively have cross-elevation splits default to always opening a
new tab. That might mitigate some of the odd behaviors. Until we actually have
support for running commands in existing windows, we'll always need to make a
new window when running elevated. We'll need to make the new window for new tabs
and splits, because there's no way to invoke another existing window.
A third proposal is to pop a warning dialog at the user when they try to open an
elevated split from and unelevated window. This dialog could be something like
> What you requested couldn't be completed. Do you want to:
> A. Make me a new tab instead.
> B. Forget it and cancel. I'll go fix my config.
I'm certainly leaning towards proposal 2 - always create a new tab. This is how
it's implemented in [#8514]. In that PR, this seems to work sensibly.
-->
After discussing with the team, we have decided that the most sensible approach
for handling a cross-elevation `split-pane` is to just create a new tab in the
elevated window. The user can always re-attach the pane as a split with the
`move-pane` command once the new pane in the elevated window.
#### Configure the Terminal to _always_ run elevated
`elevate` is a per-profile property, not a global property. If a user
wants to always have all instances of the Terminal run elevated, they
could set `"elevate": true` in their profile defaults. That would cause _all_
profiles they launch to always spawn as elevated windows.
#### `elevate` in Actions
Additionally, we'll add the `elevate` property to the `NewTerminalArgs` used in
the `newTab`, `splitPane`, and `newWindow` actions. This is similar to how other
properties of profiles can be overridden at launch time. This will allow
windows, tabs and panes to all be created specifically as elevated windows.
In the `NewTerminalArgs`, `elevate` will be an optional boolean, with the
following behavior:
* `null` (_default_): Don't modify the `elevate` property for this profile
* `true`: This launch should act like the profile had `"elevate": true` in its
properties.
* `false`: This launch should act like the profile had `"elevate": false` in its
properties.
We'll also add an iterable command for opening a profile in an
elevated tab, with the following json:
```jsonc
{
// New elevated tab...
"name": { "key": "NewElevatedTabParentCommandName", "icon": "UAC-Shield.png" },
"commands": [
{
"iterateOn": "profiles",
"icon": "${profile.icon}",
"name": "${profile.name}",
"command": { "action": "newTab", "profile": "${profile.name}", "elevated": true }
}
]
},
```
#### Elevation from the dropdown
Currently, the new tab dropdown supports opening a new pane by
<kbd>Alt+click</kbd>ing on a profile. We could similarly add support to open a
tab elevated with <kbd>Ctrl+click</kbd>. This is similar to the behavior of the
Windows taskbar. It supports creating an elevated instance of a program by
<kbd>Ctrl+click</kbd>ing on entries as well.
## Implementation Details
### Starting an elevated process from an unelevated process
It seems that we're able to create an elevated process by passing the `"runas"`
verb to
[`ShellExecute`](https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea).
So we could use something like
```c++
ShellExecute(nullptr,
L"runas",
L"wt.exe",
L"-w new new-tab [args...]",
nullptr,
SW_SHOWNORMAL);
```
This will ask the shell to perform a UAC prompt before spawning `wt.exe` as an
elevated process.
> 👉 NOTE: This mechanism won't always work on non-Desktop SKUs of Windows. For
> more discussion, see [Elevation on OneCore SKUs](#Elevation-on-OneCore-SKUs).
## Potential Issues
<table>
<tr>
<td><strong>Accessibility</strong></td>
<td>
The set of changes proposed here are not expected to introduce any new
accessibility issues. Users can already create elevated Terminal windows. Making
it easier to create these windows doesn't really change our accessibility story.
</td>
</tr>
<tr>
<td><strong>Security</strong></td>
<td>
We won't be doing anything especially unique, so there aren't expected to be any
substantial security risks associated with these changes. Users can already
create elevated Terminal windows, so we're not really introducing any new
functionality, from a security perspective.
We're relying on the inherent security of the `runas` verb of `ShellExecute` to
prevent any sort of unexpected escalation-of-privilege.
<hr>
One security concern is the fact that the `settings.json` file is currently a
totally unsecured file. It's completely writable by any medium-IL process. That
means it's totally possible for a malicious program to change the file. The
malicious program could find a user's "Elevated PowerShell" profile, and change
the commandline to `malicious.exe`. The user might then think that their
"Elevated PowerShell" will run `powershell.exe` elevated, but will actually
auto-elevate this attacker.
If all we expose to the user is the name of the profile in the UAC dialog, then
there's no way for the user to be sure that the program that's about to be
launched is actually what they expect.
To help mitigate this, we should _always_ pass the evaluated `commandline` as a
part of the call to `ShellExecute`. the arguments that are passed to
`ShellExecute` are visible to the user, though they need to click the "More
Details" dropdown to reveal them.
We will need to mitigate this vulnerability regardless of adding support for the
auto-elevation of individual terminal tabs/panes. If a user is launching the
Terminal elevated (i.e. from the Win+X menu in Windows 11), then it's possible
for a malicious program to overwrite the `commandline` of their default profile.
The user may now unknowingly invoke this malicious program while thinking they
are simply launching the Terminal.
To deal with this more broadly, we will display a dialog within the Terminal
window before creating **any** elevated terminal instance. In that dialog, we'll
display the commandline that will be executed, so the user can very easily
confirm the commandline.
This will need to happen for all elevated terminal instances. For an elevated
Windows Terminal window, this means _all_ connections made by the Terminal.
Every time the user opens a new profile or a new commandline in a pane, we'll
need to prompt them first to confirm the commandline. This dialog within the
elevated window will also prevent an attacker from editing the `settings.json`
file while the user already has an elevated Terminal window open and hijacking a
profile.
The dialog options will certainly be annoying to users who don't want to be
taken out of their flow to confirm the commandline that they wish to launch.
There's precedent for a similar warning being implemented by VSCode, with their
[Workspace Trust] feature. They too faced a similar backlash when the feature
first shipped. However, in light of recent global cybersecurity attacks, this is
seen as an acceptable UX degradation in the name of application trust. We don't
want to provide an avenue that's too easy to abuse.
When the user confirms the commandline of this profile as something safe to run,
we'll add it to an elevated-only version of `state.json`. (see [#7972] for more
details). This elevated version of the file will only be accessible by the
elevated Terminal, so an attacker cannot hijack the contents of the file. This
will help mitigate the UX discomfort caused by prompting on every commandline
launched. This should mean that the discomfort is only limited to the first
elevated launch of a particular profile. Subsequent launches (without modifying
the `commandline`) will work as they always have.
The dialog for confirming these commandlines should have a link to the docs for
"Learn more...". Transparency in the face of this dialog should
mitigate some dissatisfaction.
The dialog will _not_ appear if the user does not have a split token - if the
user's PC does not have UAC enabled, then they're _already_ running as an
Administrator. Everything they do is elevated, so they shouldn't be prompted in
this way.
The Settings UI should also expose a way of viewing and removing these cached
entries. This page should only be populated in the elevated version of the
Terminal.
</td>
</tr>
<tr>
<td><strong>Reliability</strong></td>
<td>
No changes to our reliability are expected as a part of this change.
</td>
</tr>
<tr>
<td><strong>Compatibility</strong></td>
<td>
There are no serious compatibility concerns expected with this changelist. The
new `elevate` property will be unset by default, so users will heed to opt-in
to the new auto-elevating behavior.
There is one minor concern regarding introducing the UAC shield on the window.
We're planning on using themes to configure the appearance of the shield. That
means we'll need to ship themes before the user will be able to hide the shield
again.
</td>
</tr>
<tr>
<td><strong>Performance, Power, and Efficiency</strong></td>
<td>
No changes to our performance are expected as a part of this change.
</td>
</tr>
</table>
### Centennial Applications
In the past, we've had a notoriously rough time with the Centennial app
infrastructure and running the Terminal elevated. Notably, we've had to list all
our WinRT classes in our SxS manifest so they could be activated using
unpackaged WinRT while running elevated. Additionally, there are plenty of
issues running the Terminal in an "over the shoulder" elevation (OTS) scenario.
Specifically, we're concerned with the following scenario:
* the current user account has the Terminal installed,
* but they aren't an Administrator,
* the Administrator account doesn't have the Terminal installed.
In that scenario, the user can run into issues launching the Terminal in an
elevated context (even after entering the Admin's credentials in the UAC
prompt).
This spec proposes no new mitigations for dealing with these issues. It may in
fact make them more prevalent, by making elevated contexts more easily
accessible.
Unfortunately, these issues are OS bugs that are largely out of our own control.
We will continue to apply pressure to the centennial app team internally as we
encounter these issues. They are are team best equipped to resolve these issues.
### Default Terminal & auto-elevation
In the future, when we support setting the Terminal as the "default terminal
emulator" on Windows. When that lands, we will use the `profiles.defaults`
settings to create the tab where we'll be hosting the commandline client. If the user has
`"elevate": true` in their `profiles.defaults`, we'd usually try to
auto-elevate the profile. In this scenario, however, we can't do that. The
Terminal is being invoked on behalf of the client app launching, instead of the
Terminal invoking the client application.
**2021-08-17 edit**: Now that "defterm" has shipped, we're a little more aware
of some of the limitations with packaged COM and elevation boundaries. Defterm
cannot be used with elevated processes _at all_ currently (see [#10276]). When
an elevated commandline application is launched, it will always just appear in
`conhost.exe`. Furthermore, An unelevated peasant can't communicate with an
elevated monarch so we can't toss the connection to the elevated monarch and
have them handle it.
The simplest solution here is to just _always_ ignore the `elevate` property for
incoming defterm connections. This is not an ideal solution, and one that we're
willing to revisit if/when [#10276] is ever fixed.
### Elevation on OneCore SKUs
This spec proposes using `ShellExecute` to elevate the Terminal window. However,
not all Windows SKUs have support for `ShellExecute`. Notably, the non-Desktop
SKUs, which are often referred to as "OneCore" SKUs. On these platforms, we
won't be able to use `ShellExecute` to elevate the Terminal. There might not
even be the concept of multiple elevation levels, or different users, depending
on the SKU.
Fortunately, this is a mostly hypothetical concern for the moment. Desktop is
the only publicly supported SKU for the Terminal currently. If the Terminal ever
does become available on those SKUs, we can use these proposals as mitigations.
* If elevation is supported, there must be some other way of elevating a
process. We could always use that mechanism instead.
* If elevation isn't supported (I'm thinking 10X is one of these), then we could
instead display a warning dialog whenever a user tries to open an elevated
profile.
- We could take the warning a step further. We could add another settings
validation step. This would warn the user if they try to mark any profiles
or actions as `"elevate":true`
## Future considerations
* If we wanted to go even further down the visual differentiation route, we
could consider allowing the user to set an entirely different theme ([#3327])
based on the elevation state. Something like `elevatedTheme`, to pick another
theme from the set of themes. This would allow them to force elevated windows
to have a red titlebar, for example.
* Over the course of discussion concerning appearance objects ([#8345]), it
became clear that having separate "elevated" appearances defined for
`profile`s was overly complicated. This is left as a consideration for a
possible future extension that could handle this scenario in a cleaner way.
* Similarly, we're going to leave [#3637] "different profiles when elevated vs
unelevated" for the future. This also plays into the design of "configure the
new tab dropdown" ([#1571]), and reconciling those two designs is out-of-scope
for this particular release.
* Tangentially, we may want to have a separate Terminal icon we ship with the
UAC shield present on it. This would be especially useful for the tray icon.
Since there will be different tray icon instances for elevated and unelevated
windows, having unique icons may help users identify which is which.
### De-elevating a Terminal
the original version of this spec proposed that `"elevated":false` from an
elevated Terminal window should create a new unelevated Terminal instance. The
mechanism for doing this is described in [The Old New Thing: How can I launch an
unelevated process from my elevated process, redux].
This works well when the Terminal is running unpackaged. However, de-elevating a
process does not play well with packaged centennial applications. When asking
the OS to run the packaged application from an elevated context, the system will
still create the child process _elevated_. This means the packaged version of
the Terminal won't be able to create a new unelevated Terminal instance.
From an internal mail thread:
> App model intercepts the `CreateProcess` call and redirects it to a COM
> service. The parent of a packaged app is not the launching app, its some COM
> service. So none of the parent process nonsense will work because the
> parameters you passed to `CreateProcess` arent being used to create the
> process.
If this is fixed in the future, we could theoretically re-introduce de-elevating
a profile. The original spec proposed a `"elevated": bool?` setting, with the
following behaviors:
* `null` (_default_): Don't modify the elevation level when running this profile
* `true`: If the current window is unelevated, try to create a new elevated
window to host this connection.
* `false`: If the current window is elevated, try to create a new unelevated
window to host this connection.
We could always re-introduce this setting, to supercede `elevate`.
### Change profile appearance for elevated windows
In [#3062] and [#8345], we're planning on allowing users to set different
appearances for a profile whether it's focused or not. We could do similar thing
to enable a profile to have a different appearance when elevated. In the
simplest case, this could allow the user to set `"background": "#ff0000"`. This
would make a profile always appear to have a red background when in an elevated
window.
The more specific details of this implementation are left to the spec
[Configuration object for profiles].
In discussion of that spec, we decided that it would be far too complicated to
try and overload the `unfocusedAppearance` machinery for differentiating between
elevated and unelevated versions of the same profile. Already, that would lead
to 4 states: [`appearance`, `unfocusedAppearance`, `elevatedAppearance`,
`elevatedUnfocusedAppearance`]. This would lead to a combinatorial explosion if
we decided in the future that there should also be other states for a profile.
This particular QoL improvement is currently being left as a future
consideration, should someone come up with a clever way of defining
elevated-specific settings.
<!--
Brainstorming notes for future readers:
You could have a profile that layers on an existing profile, with elevated-specific settings:
{
"name": "foo",
"background": "#0000ff",
"commandline": "cmd.exe /k echo I am unelevated"
},
{
"inheritsFrom": "foo",
"background": "#ff0000",
"elevate": true,
"commandline": "cmd.exe /k echo I am ELEVATED"
}
-->
<!-- Footnotes -->
[#632]: https://github.com/microsoft/terminal/issues/632
[#1032]: https://github.com/microsoft/terminal/issues/1032
[#1571]: https://github.com/microsoft/terminal/issues/1571
[#1939]: https://github.com/microsoft/terminal/issues/1939
[#3062]: https://github.com/microsoft/terminal/issues/3062
[#3327]: https://github.com/microsoft/terminal/issues/3327
[#3637]: https://github.com/microsoft/terminal/issues/3637
[#4472]: https://github.com/microsoft/terminal/issues/4472
[#5000]: https://github.com/microsoft/terminal/issues/5000
[#7972]: https://github.com/microsoft/terminal/pull/7972
[#8311]: https://github.com/microsoft/terminal/issues/8311
[#8345]: https://github.com/microsoft/terminal/issues/8345
[#8514]: https://github.com/microsoft/terminal/issues/8514
[#10276]: https://github.com/microsoft/terminal/issues/10276
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0.md
[Configuration object for profiles]: https://github.com/microsoft/terminal/blob/main/doc/specs/Configuration%20object%20for%20profiles.md
[Session Management Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234472%20-%20Windows%20Terminal%20Session%20Management.md
[The Old New Thing: How can I launch an unelevated process from my elevated process, redux]: https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
[Workspace Trust]: https://code.visualstudio.com/docs/editor/workspace-trust

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -0,0 +1,130 @@
---
author: Kayla Cinnamon - cinnamon-msft
created on: 2021-03-04
last updated: 2021-03-09
issue id: 6900
---
# Actions Page
## Abstract
We need to represent actions inside the settings UI. This spec goes through the possible use cases and reasoning for including specific features for actions inside the settings UI.
## Background
### Inspiration
It would be ideal if we could get the settings UI to have parity with the JSON file. This will take some design work if we want every feature possible in relation to actions. There is also the option of not having parity with the JSON file in order to present a simpler UX.
### User Stories
All of these features are possible with the JSON file. This spec will go into discussion of which (possibly all) of these user stories need to be handled by the settings UI.
- Add key bindings to an action that does not already have keys assigned
- Edit key bindings for an action
- Remove key bindings from an action
- Add multiple key bindings for the same action
- Create an iterable action
- Create a nested action
- Choose which actions appear inside the command palette
- See all possible actions, regardless of keys
Commands with properties:
- sendInput has "input"
- closeOtherTabs has "index"
- closeTabsAfter has "index"
- renameTab has "title"*
- setTabColor has "color"*
- newWindow has "commandline", "startingDirectory", "tabTitle", "index", "profile"
- splitPane has "split", "commandline", "startingDirectory", "tabTitle", "index", "profile", "splitMode", "size"
- copy has "singleLine", "copyFormatting"
- scrollUp has "rowsToScroll"
- scrollDown has "rowsToScroll"
- setColorScheme has "colorScheme"
Majority of these commands listed above are intended for the command palette, so they wouldn't make much sense with keys assigned to them anyway.
### Future Considerations
One day we'll have actions that can be invoked by items in the dropdown menu. This setting will have to live somewhere. Also, once we get a status bar, people may want to invoke actions from there.
## Solution Design
### Proposal 1: Keyboard and Command Palette pages
Implement a Keyboard page in place of the Actions page. Also plan for a Command Palette page in the future if it's something that's heavily requested. The Command Palette page would cover the missing use cases listed below.
When users want to add a new key binding, the dropdown will list every action, regardless if it already has keys assigned. This page should show every key binding assigned to an action, even if there are multiple bindings to the same action.
Users will be able to view every possible action from the command palette if they'd like.
Use cases covered:
- Add key bindings to an action that does not already have keys assigned
- Edit key bindings for an action
- Remove key bindings from an action
- Add multiple key bindings for the same action
- See all actions that have keys assigned
Use cases missing:
- Create an iterable action
- Create a nested action
- Choose which actions appear inside the command palette
- See all possible actions, regardless of keys
* **Pros**:
- This allows people to edit their actions in most of their scenarios.
- This gives us some wiggle room to cover majority of the use cases we need and seeing if people want the other use cases that are missing.
* **Cons**:
- Unfortunately we couldn't cover every single use case with this design.
- You can't edit the properties that are on some commands, however the default commands from the command palette include options with properties anyway. For example "decrease font size" has the `delta` property already included.
### Proposal 2: Have everything on one Actions page
Implement an Actions page that allows you to create actions designed for the command palette as well as actions with keys.
Use cases covered:
- Add key bindings to an action that does not already have keys assigned
- Edit key bindings for an action
- Remove key bindings from an action
- Add multiple key bindings for the same action
- See all actions that have keys assigned
- Create an iterable action
- Create a nested action
- Choose which actions appear inside the command palette
- See all possible actions, regardless of keys
I could not come up with a UX design that wasn't too complicated or confusing for this scenario.
**Pros**:
- There is full parity with the JSON file.
**Cons**:
- Could not come up with a simplistic design to represent all of the use cases (which makes the settings UI not as enticing since it promotes ease of use).
## Conclusion
We considered Proposal 2, however the design became cluttered very quickly and we agreed to create two pages and start off with Proposal 1.
## UI/UX Design
![Click edit on key binding](./edit-click.png)
The Add new button is using the secondary color, to align with the button on the Color schemes page.
![Edit key binding](./edit-keys.png)
![Click add new](./add-click.png)
![Add key binding](./add-keys.png)
## Potential Issues
This design is not 1:1 with the JSON file, so actions that don't have keys will not appear on this page. Additionally, you can't add a new action without keys with this current design.
You also cannot specify properties on commands (like the `newTab` command) and these will have to be added through the JSON file. Considering there are only a few of these and we're planning to iterate on this and add a Command Palette page, we were okay with this decision.
## Resources
### Footnotes

View File

@@ -29,8 +29,8 @@ Below is the schedule for when milestones will be included in release builds of
| 2021-03-01 | [1.7] in Windows Terminal Preview<br>[1.6] in Windows Terminal | [Windows Terminal Preview 1.7 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-7-release/) |
| 2021-04-14 | [1.8] in Windows Terminal Preview<br>[1.7] in Windows Terminal | [Windows Terminal Preview 1.8 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-8-release/) |
| 2021-05-31 | [1.9] in Windows Terminal Preview<br>[1.8] in Windows Terminal | [Windows Terminal Preview 1.9 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-9-release/) |
| 2021-07-31 | 1.10 in Windows Terminal Preview<br>[1.9] in Windows Terminal | |
| 2021-08-30 | 1.11 in Windows Terminal Preview<br>1.10 in Windows Terminal | |
| 2021-07-14 | [1.10] in Windows Terminal Preview<br>[1.9] in Windows Terminal | [Windows Terminal Preview 1.10 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-10-release/) |
| 2021-08-31 | [1.11] in Windows Terminal Preview<br>[1.10] in Windows Terminal | [Windows Terminal Preview 1.11 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-11-release/) |
| 2021-10-31 | 1.12 in Windows Terminal Preview<br>1.11 in Windows Terminal | |
| 2021-11-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
| 2021-12-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
@@ -89,6 +89,8 @@ Feature Notes:
[1.7]: https://github.com/microsoft/terminal/milestone/32
[1.8]: https://github.com/microsoft/terminal/milestone/33
[1.9]: https://github.com/microsoft/terminal/milestone/34
[1.10]: https://github.com/microsoft/terminal/milestone/35
[1.11]: https://github.com/microsoft/terminal/milestone/36
[2.0]: https://github.com/microsoft/terminal/milestone/22
[#1564]: https://github.com/microsoft/terminal/issues/1564
[#6720]: https://github.com/microsoft/terminal/pull/6720

View File

@@ -4,7 +4,7 @@ This was originally imported by @Austin-Lamb in December 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?
@@ -12,4 +12,4 @@ That provenance file is automatically read and inventoried by Microsoft systems
2. Take the parts you want, but leave most of it behind since it's HUGE and will bloat the repo to take it all. At the time of this writing, we only use small_vector.hpp and its dependencies as a header-only library.
3. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in a version-specific subdirectory below this readme.
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
4. Submit the pull.
4. Submit the pull.

View File

@@ -4,7 +4,7 @@ This was originally imported by @miniksa in January 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?

View File

@@ -4,7 +4,7 @@ This was originally imported by @miniksa in March 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?

View File

@@ -4,7 +4,7 @@ This was originally imported by @DHowett-MSFT in April 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?

View File

@@ -4,7 +4,7 @@ This was originally imported by @PankajBhojwani in September 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?

View File

@@ -4,7 +4,7 @@ This was originally imported by @miniksa in March 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?

View File

@@ -4,4 +4,4 @@ This manifest anchors our usage of rgb.txt from the X11 distribution.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -8,5 +8,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
### Fonts Included
* Cascadia Code, Cascadia Mono (2106.17)
* from microsoft/cascadia-code@fb0bce69c1c12f6c298b8bc1c1d181868f5daa9a
* Cascadia Code, Cascadia Mono (2108.26)
* from microsoft/cascadia-code@f91d08f703ee61cf4ae936b9700ca974de2748fe

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />

View File

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

View File

@@ -95,16 +95,18 @@ bool TextAttribute::IsLegacy() const noexcept
// - defaultFgColor: the default foreground color rgb value.
// - defaultBgColor: the default background color rgb value.
// - reverseScreenMode: true if the screen mode is reversed.
// - blinkingIsFaint: true if blinking should be interpreted as faint.
// - blinkingIsFaint: true if blinking should be interpreted as faint. (defaults to false)
// - boldIsBright: true if "bold" should be interpreted as bright. (defaults to true)
// Return Value:
// - the foreground and background colors that should be displayed.
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const std::array<COLORREF, 256>& colorTable,
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode,
const bool blinkingIsFaint) const noexcept
const bool blinkingIsFaint,
const bool boldIsBright) const noexcept
{
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold());
auto fg = _foreground.GetColor(colorTable, defaultFgColor, boldIsBright && IsBold());
auto bg = _background.GetColor(colorTable, defaultBgColor);
if (IsFaint() || (IsBlinking() && blinkingIsFaint))
{

View File

@@ -64,11 +64,12 @@ public:
static TextAttribute StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept;
WORD GetLegacyAttributes() const noexcept;
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
std::pair<COLORREF, COLORREF> CalculateRgbColors(const std::array<COLORREF, 256>& colorTable,
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode = false,
const bool blinkingIsFaint = false) const noexcept;
const bool blinkingIsFaint = false,
const bool boldIsBright = true) const noexcept;
bool IsLeadingByte() const noexcept;
bool IsTrailingByte() const noexcept;
@@ -212,7 +213,7 @@ constexpr bool operator!=(const TextAttribute& a, const TextAttribute& b) noexce
#ifdef UNIT_TESTING
#define LOG_ATTR(attr) (Log::Comment(NoThrowString().Format( \
L#attr L"=%s", VerifyOutputTraits<TextAttribute>::ToString(attr).GetBuffer())))
L## #attr L"=%s", VerifyOutputTraits<TextAttribute>::ToString(attr).GetBuffer())))
namespace WEX
{

View File

@@ -50,6 +50,9 @@ constexpr std::array<BYTE, 256> Index256ToIndex16 = {
// clang-format on
// We should only need 4B for TextColor. Any more than that is just waste.
static_assert(sizeof(TextColor) == 4);
bool TextColor::CanBeBrightened() const noexcept
{
return IsIndex16() || IsDefault();
@@ -138,15 +141,12 @@ void TextColor::SetDefault() noexcept
// - brighten: if true, we'll brighten a dark color table index.
// Return Value:
// - a COLORREF containing the real value of this TextColor.
COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
const COLORREF defaultColor,
bool brighten) const noexcept
COLORREF TextColor::GetColor(const std::array<COLORREF, 256>& colorTable, const COLORREF defaultColor, bool brighten) const noexcept
{
if (IsDefault())
{
if (brighten)
{
FAIL_FAST_IF(colorTable.size() < 16);
// See MSFT:20266024 for context on this fix.
// Additionally todo MSFT:20271956 to fix this better for 19H2+
// If we're a default color, check to see if the defaultColor exists
@@ -156,6 +156,61 @@ COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
// (Settings::_DefaultForeground==INVALID_COLOR, and the index
// from _wFillAttribute is being used instead.)
// If we find a match, return instead the bright version of this color
static_assert(sizeof(COLORREF) * 8 == 32, "The vectorized code broke. If you can't fix COLORREF, just remove the vectorized code.");
#pragma warning(push)
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
#ifdef __AVX2__
// I wrote this vectorized code one day, because the sun was shining so nicely.
// There's no other reason for this to exist here, except for being pretty.
// This code implements the exact same for loop you can find below, but is ~3x faster.
//
// A brief explanation for people unfamiliar with vectorized instructions:
// Vectorized instructions, like "SSE" or "AVX", allow you to run
// common operations like additions, multiplications, comparisons,
// or bitwise operations concurrently on multiple values at once.
//
// We want to find the given defaultColor in the first 8 values of colorTable.
// Coincidentally a COLORREF is a DWORD and 8 of them are exactly 256 bits.
// -- The size of a single AVX register.
//
// Thus, the code works like this:
// 1. Load all 8 DWORDs at once into one register
// 2. Set the same defaultColor 8 times in another register
// 3. Compare all 8 values at once
// The result is either 0xffffffff or 0x00000000.
// 4. Extract the most significant bit of each DWORD
// Assuming that no duplicate colors exist in colorTable,
// the result will be something like 0b00100000.
// 5. Use BitScanForward (bsf) to find the index of the most significant 1 bit.
const auto haystack = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(colorTable.data())); // 1.
const auto needle = _mm256_set1_epi32(__builtin_bit_cast(int, defaultColor)); // 2.
const auto result = _mm256_cmpeq_epi32(haystack, needle); // 3.
const auto mask = _mm256_movemask_ps(_mm256_castsi256_ps(result)); // 4.
unsigned long index;
return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast<size_t>(index) + 8) : defaultColor; // 5.
#elif _M_AMD64
// If you look closely this SSE2 algorithm is the same as the AVX one.
// The two differences are that we need to:
// * do everything twice, because SSE is limited to 128 bits and not 256.
// * use _mm_packs_epi32 to merge two 128 bits vectors into one in step 3.5.
// _mm_packs_epi32 takes two SSE registers and truncates all 8 DWORDs into 8 WORDs,
// the latter of which fits into a single register (which is then used in the identical step 4).
// * since the result are now 8 WORDs, we need to use _mm_movemask_epi8 (there's no 16-bit variant),
// which unlike AVX's step 4 results in in something like 0b0000110000000000.
// --> the index returned by _BitScanForward must be divided by 2.
const auto haystack1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(colorTable.data() + 0));
const auto haystack2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(colorTable.data() + 4));
const auto needle = _mm_set1_epi32(__builtin_bit_cast(int, defaultColor));
const auto result1 = _mm_cmpeq_epi32(haystack1, needle);
const auto result2 = _mm_cmpeq_epi32(haystack2, needle);
const auto result = _mm_packs_epi32(result1, result2); // 3.5
const auto mask = _mm_movemask_epi8(result);
unsigned long index;
return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast<size_t>(index / 2) + 8) : defaultColor;
#else
for (size_t i = 0; i < 8; i++)
{
if (til::at(colorTable, i) == defaultColor)
@@ -163,6 +218,8 @@ COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
return til::at(colorTable, i + 8);
}
}
#endif
#pragma warning(pop)
}
return defaultColor;
@@ -199,7 +256,7 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept
}
else if (IsIndex256())
{
return Index256ToIndex16.at(GetIndex());
return til::at(Index256ToIndex16, GetIndex());
}
else
{
@@ -208,7 +265,7 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept
const BYTE compressedRgb = (_red & 0b11100000) +
((_green >> 3) & 0b00011100) +
((_blue >> 6) & 0b00000011);
return CompressedRgbToIndex16.at(compressedRgb);
return til::at(CompressedRgbToIndex16, compressedRgb);
}
}

View File

@@ -86,10 +86,7 @@ public:
void SetIndex(const BYTE index, const bool isIndex256) noexcept;
void SetDefault() noexcept;
COLORREF GetColor(gsl::span<const COLORREF> colorTable,
const COLORREF defaultColor,
const bool brighten = false) const noexcept;
COLORREF GetColor(const std::array<COLORREF, 256>& colorTable, const COLORREF defaultColor, bool brighten = false) const noexcept;
BYTE GetLegacyIndex(const BYTE defaultIndex) const noexcept;
constexpr BYTE GetIndex() const noexcept
@@ -157,5 +154,3 @@ namespace WEX
}
}
#endif
static_assert(sizeof(TextColor) <= 4 * sizeof(BYTE), "We should only need 4B for an entire TextColor. Any more than that is just waste");

View File

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

View File

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

View File

@@ -44,7 +44,7 @@
<ClInclude Include="..\Row.hpp" />
<ClInclude Include="..\search.h" />
<ClInclude Include="..\TextColor.h" />
<ClInclude Include="..\TextAttribute.h" />
<ClInclude Include="..\TextAttribute.hpp" />
<ClInclude Include="..\textBuffer.hpp" />
<ClInclude Include="..\textBufferCellIterator.hpp" />
<ClInclude Include="..\textBufferTextIterator.hpp" />

View File

@@ -418,7 +418,6 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
// Store character and double byte data
CharRow& charRow = Row.GetCharRow();
short const cBufferWidth = GetSize().Width();
try
{
@@ -1650,13 +1649,14 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
if (!cell.DbcsAttr().IsTrailing())
{
selectionText.append(cell.Chars());
const auto chars = cell.Chars();
selectionText.append(chars);
if (copyTextColor)
{
const auto cellData = cell.TextAttr();
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
for (const wchar_t wch : cell.Chars())
for (size_t j = 0; j < chars.size(); ++j)
{
selectionFgAttr.push_back(CellFgAttr);
selectionBkAttr.push_back(CellBkAttr);

View File

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

View File

@@ -22,12 +22,11 @@ class TextAttributeTests
TEST_METHOD(TestTextAttributeColorGetters);
TEST_METHOD(TestReverseDefaultColors);
TEST_METHOD(TestRoundtripDefaultColors);
TEST_METHOD(TestBoldAsBright);
static const int COLOR_TABLE_SIZE = 16;
COLORREF _colorTable[COLOR_TABLE_SIZE];
std::array<COLORREF, 256> _colorTable;
COLORREF _defaultFg = RGB(1, 2, 3);
COLORREF _defaultBg = RGB(4, 5, 6);
gsl::span<const COLORREF> _GetTableView();
};
bool TextAttributeTests::ClassSetup()
@@ -51,11 +50,6 @@ bool TextAttributeTests::ClassSetup()
return true;
}
gsl::span<const COLORREF> TextAttributeTests::_GetTableView()
{
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
}
void TextAttributeTests::TestRoundtripLegacy()
{
WORD expectedLegacy = FOREGROUND_BLUE | BACKGROUND_RED;
@@ -133,23 +127,22 @@ void TextAttributeTests::TestTextAttributeColorGetters()
const COLORREF faintRed = RGB(127, 0, 0);
const COLORREF green = RGB(0, 255, 0);
TextAttribute attr(red, green);
auto view = _GetTableView();
// verify that calculated foreground/background are the same as the direct
// values when reverse video is not set
VERIFY_IS_FALSE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, calculated foreground/background values should be
// switched while getters stay the same
attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// reset the reverse video
attr.SetReverseVideo(false);
@@ -158,17 +151,17 @@ void TextAttributeTests::TestTextAttributeColorGetters()
// while the background and getters stay the same
attr.SetFaint(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, calculated foreground/background values should be
// switched, and the background fainter, while getters stay the same
attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// reset the reverse video and faint attributes
attr.SetReverseVideo(false);
@@ -178,17 +171,17 @@ void TextAttributeTests::TestTextAttributeColorGetters()
// background, while getters stay the same
attr.SetInvisible(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, the calculated background value should match
// the foreground, while getters stay the same
attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
}
void TextAttributeTests::TestReverseDefaultColors()
@@ -196,40 +189,39 @@ void TextAttributeTests::TestReverseDefaultColors()
const COLORREF red = RGB(255, 0, 0);
const COLORREF green = RGB(0, 255, 0);
TextAttribute attr{};
auto view = _GetTableView();
// verify that calculated foreground/background are the same as the direct
// values when reverse video is not set
VERIFY_IS_FALSE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, calculated foreground/background values should be
// switched while getters stay the same
attr.SetReverseVideo(true);
VERIFY_IS_TRUE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
attr.SetForeground(red);
VERIFY_IS_TRUE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
attr.Invert();
VERIFY_IS_FALSE(attr.IsReverseVideo());
attr.SetDefaultForeground();
attr.SetBackground(green);
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
}
void TextAttributeTests::TestRoundtripDefaultColors()
@@ -272,3 +264,56 @@ void TextAttributeTests::TestRoundtripDefaultColors()
// Reset the legacy default colors to white on black.
TextAttribute::SetLegacyDefaultAttributes(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
void TextAttributeTests::TestBoldAsBright()
{
const COLORREF darkBlack = til::at(_colorTable, 0);
const COLORREF brightBlack = til::at(_colorTable, 8);
const COLORREF darkGreen = til::at(_colorTable, 2);
TextAttribute attr{};
// verify that calculated foreground/background are the same as the direct
// values when not bold
VERIFY_IS_FALSE(attr.IsBold());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
// with bold set, calculated foreground/background values shouldn't change for the default colors.
attr.SetBold(true);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetIndexedForeground(0);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"Foreground should be bright black when bold is bright is enabled");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
Log::Comment(L"Foreground should be dark black when bold is bright is disabled");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetIndexedBackground(2);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"background should be unaffected by 'bold is bright'");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetBold(false);
VERIFY_IS_FALSE(attr.IsBold());
Log::Comment(L"when not bold, 'bold is bright' changes nothing");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
Log::Comment(L"When set to a bright color, and bold, 'bold is bright' changes nothing");
attr.SetBold(true);
attr.SetIndexedForeground(8);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
}

View File

@@ -23,11 +23,9 @@ class TextColorTests
TEST_METHOD(TestRgbColor);
TEST_METHOD(TestChangeColor);
static const int COLOR_TABLE_SIZE = 16;
COLORREF _colorTable[COLOR_TABLE_SIZE];
std::array<COLORREF, 256> _colorTable;
COLORREF _defaultFg = RGB(1, 2, 3);
COLORREF _defaultBg = RGB(4, 5, 6);
gsl::span<const COLORREF> _GetTableView();
};
bool TextColorTests::ClassSetup()
@@ -51,11 +49,6 @@ bool TextColorTests::ClassSetup()
return true;
}
gsl::span<const COLORREF> TextColorTests::_GetTableView()
{
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
}
void TextColorTests::TestDefaultColor()
{
TextColor defaultColor;
@@ -64,18 +57,16 @@ void TextColorTests::TestDefaultColor()
VERIFY_IS_FALSE(defaultColor.IsLegacy());
VERIFY_IS_FALSE(defaultColor.IsRgb());
auto view = _GetTableView();
auto color = defaultColor.GetColor(view, _defaultFg, false);
auto color = defaultColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_defaultFg, color);
color = defaultColor.GetColor(view, _defaultFg, true);
color = defaultColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_defaultFg, color);
color = defaultColor.GetColor(view, _defaultBg, false);
color = defaultColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_defaultBg, color);
color = defaultColor.GetColor(view, _defaultBg, true);
color = defaultColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_defaultBg, color);
}
@@ -87,18 +78,16 @@ void TextColorTests::TestDarkIndexColor()
VERIFY_IS_TRUE(indexColor.IsLegacy());
VERIFY_IS_FALSE(indexColor.IsRgb());
auto view = _GetTableView();
auto color = indexColor.GetColor(view, _defaultFg, false);
auto color = indexColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color);
color = indexColor.GetColor(view, _defaultFg, true);
color = indexColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultBg, false);
color = indexColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color);
color = indexColor.GetColor(view, _defaultBg, true);
color = indexColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
}
@@ -110,18 +99,16 @@ void TextColorTests::TestBrightIndexColor()
VERIFY_IS_TRUE(indexColor.IsLegacy());
VERIFY_IS_FALSE(indexColor.IsRgb());
auto view = _GetTableView();
auto color = indexColor.GetColor(view, _defaultFg, false);
auto color = indexColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultFg, true);
color = indexColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultBg, false);
color = indexColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultBg, true);
color = indexColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
}
@@ -134,18 +121,16 @@ void TextColorTests::TestRgbColor()
VERIFY_IS_FALSE(rgbColor.IsLegacy());
VERIFY_IS_TRUE(rgbColor.IsRgb());
auto view = _GetTableView();
auto color = rgbColor.GetColor(view, _defaultFg, false);
auto color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultFg, true);
color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, false);
color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, true);
color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(myColor, color);
}
@@ -158,57 +143,55 @@ void TextColorTests::TestChangeColor()
VERIFY_IS_FALSE(rgbColor.IsLegacy());
VERIFY_IS_TRUE(rgbColor.IsRgb());
auto view = _GetTableView();
auto color = rgbColor.GetColor(view, _defaultFg, false);
auto color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultFg, true);
color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, false);
color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, true);
color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(myColor, color);
rgbColor.SetDefault();
color = rgbColor.GetColor(view, _defaultFg, false);
color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_defaultFg, color);
color = rgbColor.GetColor(view, _defaultFg, true);
color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_defaultFg, color);
color = rgbColor.GetColor(view, _defaultBg, false);
color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_defaultBg, color);
color = rgbColor.GetColor(view, _defaultBg, true);
color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_defaultBg, color);
rgbColor.SetIndex(7, false);
color = rgbColor.GetColor(view, _defaultFg, false);
color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color);
color = rgbColor.GetColor(view, _defaultFg, true);
color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultBg, false);
color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color);
color = rgbColor.GetColor(view, _defaultBg, true);
color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
rgbColor.SetIndex(15, false);
color = rgbColor.GetColor(view, _defaultFg, false);
color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultFg, true);
color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultBg, false);
color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultBg, true);
color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color);
}

View File

@@ -397,6 +397,10 @@ namespace SettingsModelLocalTests
"name":"action6",
"command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" }
},
{
"name":"action7_startingDirectoryWithTrailingSlash",
"command": { "action": "newWindow", "startingDirectory":"C:\\", "commandline": "bar.exe" }
},
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
@@ -405,7 +409,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(7u, commands.Size());
VERIFY_ARE_EQUAL(8u, commands.Size());
{
auto command = commands.Lookup(L"action0");
@@ -503,5 +507,20 @@ namespace SettingsModelLocalTests
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline());
}
{
auto command = commands.Lookup(L"action7_startingDirectoryWithTrailingSlash");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
Log::Comment(NoThrowString().Format(
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\\\\" -- \"bar.exe\"", terminalArgs.ToCommandline());
}
}
}

View File

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

View File

@@ -36,10 +36,15 @@ namespace SettingsModelLocalTests
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(KeyChords);
TEST_METHOD(ManyKeysSameAction);
TEST_METHOD(LayerKeybindings);
TEST_METHOD(UnbindKeybindings);
TEST_METHOD(LayerScancodeKeybindings);
TEST_METHOD(TestExplicitUnbind);
TEST_METHOD(TestArbitraryArgs);
TEST_METHOD(TestSplitPaneArgs);
@@ -53,6 +58,7 @@ namespace SettingsModelLocalTests
TEST_METHOD(TestMoveTabArgs);
TEST_METHOD(TestGetKeyBindingForAction);
TEST_METHOD(KeybindingsWithoutVkey);
TEST_CLASS_SETUP(ClassSetup)
{
@@ -61,6 +67,69 @@ namespace SettingsModelLocalTests
}
};
void KeyBindingsTests::KeyChords()
{
struct testCase
{
VirtualKeyModifiers modifiers;
int32_t vkey;
int32_t scanCode;
std::wstring_view expected;
};
static constexpr std::array testCases{
testCase{
VirtualKeyModifiers::None,
'A',
0,
L"a",
},
testCase{
VirtualKeyModifiers::Control,
'A',
0,
L"ctrl+a",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift,
VK_OEM_PLUS,
0,
L"ctrl+shift+plus",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
255,
0,
L"win+ctrl+alt+shift+vk(255)",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
0,
123,
L"win+ctrl+alt+shift+sc(123)",
},
};
for (const auto& tc : testCases)
{
Log::Comment(NoThrowString().Format(L"Testing case:\"%s\"", tc.expected.data()));
const auto actualString = KeyChordSerialization::ToString({ tc.modifiers, tc.vkey, tc.scanCode });
VERIFY_ARE_EQUAL(tc.expected, actualString);
auto expectedVkey = tc.vkey;
if (!expectedVkey)
{
expectedVkey = MapVirtualKeyW(tc.scanCode, MAPVK_VSC_TO_VK_EX);
}
const auto actualKeyChord = KeyChordSerialization::FromString(actualString);
VERIFY_ARE_EQUAL(tc.modifiers, actualKeyChord.Modifiers());
VERIFY_ARE_EQUAL(expectedVkey, actualKeyChord.Vkey());
VERIFY_ARE_EQUAL(tc.scanCode, actualKeyChord.ScanCode());
}
}
void KeyBindingsTests::ManyKeysSameAction()
{
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
@@ -75,7 +144,6 @@ namespace SettingsModelLocalTests
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
@@ -99,7 +167,6 @@ namespace SettingsModelLocalTests
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
@@ -129,7 +196,6 @@ namespace SettingsModelLocalTests
const auto bindings5Json = VerifyParseSucceeded(bindings5String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
@@ -142,7 +208,7 @@ namespace SettingsModelLocalTests
L"Try unbinding a key using `\"unbound\"` to unbind the key"));
actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using `null` to unbind the key"));
@@ -152,7 +218,7 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting
actionMap->LayerJson(bindings3Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using an unrecognized command to unbind the key"));
@@ -162,7 +228,7 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting
actionMap->LayerJson(bindings4Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using a straight up invalid value to unbind the key"));
@@ -172,13 +238,38 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting
actionMap->LayerJson(bindings5Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format(
L"Try unbinding a key that wasn't bound at all"));
actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
}
void KeyBindingsTests::TestExplicitUnbind()
{
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
const std::string bindings1String{ R"([ { "command": "unbound", "keys": ["ctrl+c"] } ])" };
const std::string bindings2String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
const KeyChord keyChord{ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 };
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings0Json);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings1Json);
VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings2Json);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
}
void KeyBindingsTests::TestArbitraryArgs()
@@ -203,7 +294,6 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size());
@@ -211,10 +301,9 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` without args parses as Copy(SingleLine=false)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -222,10 +311,9 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('C') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -233,10 +321,9 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ false, true, true, static_cast<int32_t>('C') };
KeyChord kc{ false, true, true, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.SingleLine());
}
@@ -244,11 +331,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` without args parses as NewTab(Index=null)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('T') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('T'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NULL(realArgs.TerminalArgs().ProfileIndex());
@@ -256,11 +342,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` parses args correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('T') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('T'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
@@ -270,11 +355,10 @@ namespace SettingsModelLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` with an index greater than the legacy "
L"args afforded parses correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('Y') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('Y'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
@@ -284,11 +368,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` ignores args it doesn't understand"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -296,11 +379,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` null as it's `args` parses as the default option"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -308,11 +390,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a positive delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(1, realArgs.Delta());
}
@@ -320,11 +401,10 @@ namespace SettingsModelLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a negative delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(-1, realArgs.Delta());
}
@@ -342,44 +422,39 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
@@ -396,37 +471,33 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TabColor());
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
VERIFY_ARE_EQUAL(til::color(0x563412), til::color(realArgs.TabColor().Value()));
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
@@ -441,16 +512,14 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine());
}
@@ -470,63 +539,56 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size());
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll());
}
{
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
}
{
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
@@ -535,7 +597,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
}
@@ -551,26 +612,23 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Forward);
}
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Backward);
}
@@ -584,7 +642,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
}
@@ -601,35 +658,31 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
{
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
}
{
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
// Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::CommandLine);
}
@@ -637,7 +690,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
}
@@ -669,7 +721,6 @@ namespace SettingsModelLocalTests
};
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
{
@@ -677,7 +728,7 @@ namespace SettingsModelLocalTests
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }, kbd);
}
{
Log::Comment(L"command with args");
@@ -688,7 +739,7 @@ namespace SettingsModelLocalTests
args->SingleLine(true);
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }, kbd);
}
{
Log::Comment(L"command with new terminal args");
@@ -700,7 +751,7 @@ namespace SettingsModelLocalTests
auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) };
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }, kbd);
}
{
Log::Comment(L"command with hidden args");
@@ -708,7 +759,45 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P') }, kbd);
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P'), 0 }, kbd);
}
}
void KeyBindingsTests::LayerScancodeKeybindings()
{
Log::Comment(L"Layering a keybinding with a character literal on top of"
L" an equivalent sc() key should replace it.");
// Wrap the first one in `R"!(...)!"` because it has `()` internally.
const std::string bindings0String{ R"!([ { "command": "quakeMode", "keys":"win+sc(41)" } ])!" };
const std::string bindings1String{ R"([ { "keys": "win+`", "command": { "action": "globalSummon", "monitor": "any" } } ])" };
const std::string bindings2String{ R"([ { "keys": "ctrl+shift+`", "command": { "action": "quakeMode" } } ])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size(), L"Layering the second action should replace the first one.");
actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
}
void KeyBindingsTests::KeybindingsWithoutVkey()
{
const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "keys":"shift+sc(255)"}])!");
const auto actionMap = winrt::make_self<implementation::ActionMap>();
actionMap->LayerJson(json);
const auto action = actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Shift, 0, 255 });
VERIFY_IS_NOT_NULL(action);
}
}

View File

@@ -37,11 +37,13 @@ namespace SettingsModelLocalTests
TEST_METHOD(TestTerminalArgsForBinding);
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
TEST_METHOD(MakeSettingsForProfile);
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
TEST_METHOD(TestLayerProfileOnColorScheme);
TEST_METHOD(TestCommandlineToTitlePromotion);
TEST_CLASS_SETUP(ClassSetup)
{
return true;
@@ -63,7 +65,7 @@ namespace SettingsModelLocalTests
const std::string settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
"profiles": { "list": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
@@ -82,6 +84,9 @@ namespace SettingsModelLocalTests
"commandline": "wsl.exe"
}
],
"defaults": {
"historySize": 29
} },
"keybindings": [
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } },
@@ -113,7 +118,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -126,15 +131,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -148,15 +153,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -170,15 +175,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -192,15 +197,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(profile2Guid, guid);
VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -214,15 +219,24 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid);
if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled())
{
// This action specified a command but no profile; it gets reassigned to the base profile
VERIFY_ARE_EQUAL(settings.ProfileDefaults(), profile);
VERIFY_ARE_EQUAL(29, termSettings.HistorySize());
}
else
{
VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@@ -237,15 +251,15 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -257,15 +271,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -278,16 +292,16 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('I'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -301,16 +315,16 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(profile2Guid, guid);
VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('J') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('J'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -323,16 +337,16 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('K') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('K'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -346,16 +360,16 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(profile2Guid, guid);
VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('L') };
KeyChord kc{ true, false, false, false, static_cast<int32_t>('L'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@@ -371,10 +385,10 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
@@ -382,9 +396,9 @@ namespace SettingsModelLocalTests
}
}
void TerminalSettingsTests::MakeSettingsForProfileThatDoesntExist()
void TerminalSettingsTests::MakeSettingsForProfile()
{
// Test that making settings throws when the GUID doesn't exist
// Test that making settings generally works.
const std::string settingsString{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
@@ -405,32 +419,32 @@ namespace SettingsModelLocalTests
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
const auto profile1 = settings.FindProfile(guid1);
const auto profile2 = settings.FindProfile(guid2);
try
{
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid1, nullptr) };
auto terminalSettings{ TerminalSettings::CreateWithProfile(settings, profile1, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(1, terminalSettings.DefaultSettings().HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
VERIFY_IS_TRUE(false, L"This call to CreateWithProfile should succeed");
}
try
{
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid2, nullptr) };
auto terminalSettings{ TerminalSettings::CreateWithProfile(settings, profile2, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(2, terminalSettings.DefaultSettings().HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
VERIFY_IS_TRUE(false, L"This call to CreateWithProfile should succeed");
}
VERIFY_THROWS(auto terminalSettings = TerminalSettings::CreateWithProfileByID(settings, guid3, nullptr), wil::ResultException, L"This call to constructor should fail");
try
{
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };
@@ -554,4 +568,85 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme)
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default
}
void TerminalSettingsTests::TestCommandlineToTitlePromotion()
{
const std::string settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": { "list": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe"
},
],
"defaults": {
"historySize": 29
} }
})" };
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
CascadiaSettings settings{ til::u8u16(settingsJson) };
{ // just a profile (profile wins)
NewTerminalArgs args{};
args.Profile(L"profile0");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle());
}
{ // profile and command line -> no promotion (profile wins)
NewTerminalArgs args{};
args.Profile(L"profile0");
args.Commandline(L"foo.exe");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle());
}
{ // just a title -> it is propagated
NewTerminalArgs args{};
args.TabTitle(L"Analog Kid");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"Analog Kid", settingsStruct.DefaultSettings().StartingTitle());
}
{ // title and command line -> no promotion
NewTerminalArgs args{};
args.TabTitle(L"Digital Man");
args.Commandline(L"foo.exe");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"Digital Man", settingsStruct.DefaultSettings().StartingTitle());
}
{ // just a commandline -> promotion
NewTerminalArgs args{};
args.Commandline(L"foo.exe");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle());
}
// various typesof commandline follow
{
NewTerminalArgs args{};
args.Commandline(L"foo.exe bar");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle());
}
{
NewTerminalArgs args{};
args.Commandline(L"\"foo exe.exe\" bar");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"foo exe.exe", settingsStruct.DefaultSettings().StartingTitle());
}
{
NewTerminalArgs args{};
args.Commandline(L"\"\" grand designs");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle());
}
{
NewTerminalArgs args{};
args.Commandline(L" imagine a man");
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle());
}
}
}

View File

@@ -61,6 +61,9 @@ Author(s):
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <til/mutex.h>
#include <til/throttled_func.h>
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"

View File

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

View File

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

View File

@@ -115,7 +115,7 @@
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore">
<HintPath>$(OpenConsoleDir)\packages\Microsoft.Taef.10.58.210305002\lib\Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore.winmd</HintPath>
<HintPath>$(OpenConsoleDir)\packages\Microsoft.Taef.10.60.210621002\lib\Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<!-- This path is _relative to the .winmd_ -->

View File

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

View File

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

View File

@@ -28,8 +28,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
CATCH_LOG();
}
// This is a private constructor to be used in unit tests, where we don't
// want each Monarch to necessarily use the current PID.
// This constructor is intended to be used in unit tests,
// but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Monarch::Monarch(const uint64_t testPID) :
_ourPID{ testPID }
{
@@ -78,6 +80,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows });
peasant.RenameRequested({ this, &Monarch::_renameRequested });
peasant.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
peasant.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
peasant.QuitAllRequested({ this, &Monarch::_handleQuitAll });
_peasants[newPeasantsId] = peasant;
TraceLoggingWrite(g_hRemotingProvider,
@@ -86,6 +92,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingUInt64(newPeasantsId, "peasantID", "the ID of the new peasant"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
_WindowCreatedHandlers(nullptr, nullptr);
return newPeasantsId;
}
catch (...)
@@ -102,6 +110,74 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}
// Method Description:
// - Gives the host process an opportunity to run any pre-close logic then
// requests all peasants to close.
// Arguments:
// - <none> used
// Return Value:
// - <none>
void Monarch::_handleQuitAll(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
// Let the process hosting the monarch run any needed logic before
// closing all windows.
_QuitAllRequestedHandlers(*this, nullptr);
// Tell all peasants to exit.
const auto callback = [&](const auto& /*id*/, const auto& p) {
p.Quit();
};
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_handleQuitAll_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not close"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(callback, onError);
}
// Method Description:
// - Tells the monarch that a peasant is being closed.
// Arguments:
// - peasantId: the id of the peasant
// Return Value:
// - <none>
void Monarch::SignalClose(const uint64_t peasantId)
{
_peasants.erase(peasantId);
_WindowClosedHandlers(nullptr, nullptr);
}
// Method Description:
// - Counts the number of living peasants.
// Arguments:
// - <none>
// Return Value:
// - the number of active peasants.
uint64_t Monarch::GetNumberOfPeasants()
{
auto num = 0;
auto callback = [&](const auto& /*id*/, const auto& p) {
// Check that the peasant is alive, and if so increment the count
p.GetID();
num += 1;
};
auto onError = [](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_GetNumberOfPeasants_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not enumerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(callback, onError);
return num;
}
// Method Description:
// - Event handler for the Peasant::WindowActivated event. Used as an
// opportunity for us to update our internal stack of the "most recent
@@ -201,6 +277,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_clearOldMruEntries(id);
}
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_lookupPeasantIdForName",
TraceLoggingWideString(std::wstring{ name }.c_str(), "name", "the name we're looking for"),
TraceLoggingUInt64(result, "peasantID", "the ID of the peasant with that name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
return result;
}
@@ -602,39 +684,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return *result;
}
// Method Description:
// - Helper for doing something on each and every peasant, with no regard
// for if the peasant is living or dead.
// - We'll try calling callback on every peasant.
// - If any single peasant is dead, then we'll call errorCallback, and move on.
// - We're taking an errorCallback here, because the thing we usually want
// to do is TraceLog a message, but TraceLoggingWrite is actually a macro
// that _requires_ the second arg to be a string literal. It can't just be
// a variable.
// Arguments:
// - callback: The function to call on each peasant
// - errorCallback: The function to call if a peasant is dead.
// Return Value:
// - <none>
void Monarch::_forAllPeasantsIgnoringTheDead(std::function<void(const Remoting::IPeasant&, const uint64_t)> callback,
std::function<void(const uint64_t)> errorCallback)
{
for (const auto& [id, p] : _peasants)
{
try
{
callback(p, id);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
// If this fails, we don't _really_ care. Just move on to the
// next one. Someone else will clean up the dead peasant.
errorCallback(id);
}
}
}
// Method Description:
// - This is an event handler for the IdentifyWindowsRequested event. A
// Peasant may raise that event if they want _all_ windows to identify
@@ -649,17 +698,18 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
// Notify all the peasants to display their ID.
auto callback = [](auto&& p, auto&& /*id*/) {
const auto callback = [&](const auto& /*id*/, const auto& p) {
p.DisplayWindowId();
};
auto onError = [](auto&& id) {
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_identifyWindows_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
_forEachPeasant(callback, onError);
}
// Method Description:
@@ -732,24 +782,55 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
try
{
args.FoundMatch(false);
// If a WindowID is provided from the args, use that first.
uint64_t windowId = 0;
// If no name was provided, then just summon the MRU window.
if (searchedForName.empty())
if (args.WindowID())
{
// Use the value of the `desktop` arg to determine if we should
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
windowId = args.WindowID().Value();
}
else
{
// Try to find a peasant that currently has this name
windowId = _lookupPeasantIdForName(searchedForName);
// If no name was provided, then just summon the MRU window.
if (searchedForName.empty())
{
// Use the value of the `desktop` arg to determine if we should
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
}
else
{
// Try to find a peasant that currently has this name
windowId = _lookupPeasantIdForName(searchedForName);
}
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
targetPeasant.Summon(args.SummonBehavior());
args.FoundMatch(true);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonWindow_Success",
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
TraceLoggingBoolean(args.SummonBehavior().MoveToCurrentDesktop(), "MoveToCurrentDesktop", "if true, move the window to the current virtual desktop"),
TraceLoggingBoolean(args.SummonBehavior().ToggleVisibility(), "ToggleVisibility", "true if we should toggle the visibility of the window"),
TraceLoggingUInt32(args.SummonBehavior().DropdownDuration(), "DropdownDuration", "the duration to dropdown the window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonWindow_NoPeasant",
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
catch (...)
@@ -762,4 +843,76 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
// Method Description:
// - This method creates a map of peasant IDs to peasant names
// while removing dead peasants.
// Arguments:
// - <none>
// Return Value:
// - A map of peasant IDs to their names.
Windows::Foundation::Collections::IVectorView<PeasantInfo> Monarch::GetPeasantInfos()
{
std::vector<PeasantInfo> names;
names.reserve(_peasants.size());
const auto func = [&](const auto& id, const auto& p) -> void {
names.push_back({ id, p.WindowName(), p.ActiveTabTitle() });
};
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_identifyWindows_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(func, onError);
return winrt::single_threaded_vector<PeasantInfo>(std::move(names)).GetView();
}
bool Monarch::DoesQuakeWindowExist()
{
bool result = false;
const auto func = [&](const auto& /*id*/, const auto& p) {
if (p.WindowName() == QuakeWindowName)
{
result = true;
}
// continue if we didn't get a positive result
return !result;
};
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_DoesQuakeWindowExist_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not ask for its name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(func, onError);
return result;
}
void Monarch::SummonAllWindows()
{
const auto func = [&](const auto& /*id*/, const auto& p) {
SummonWindowBehavior args{};
args.ToggleVisibility(false);
p.Summon(args);
};
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonAll_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(func, onError);
}
}

View File

@@ -41,20 +41,32 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
struct Monarch : public MonarchT<Monarch>
{
Monarch();
Monarch(const uint64_t testPID);
~Monarch();
uint64_t GetPID();
uint64_t AddPeasant(winrt::Microsoft::Terminal::Remoting::IPeasant peasant);
void SignalClose(const uint64_t peasantId);
uint64_t GetNumberOfPeasants();
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
bool DoesQuakeWindowExist();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
Monarch(const uint64_t testPID);
uint64_t _ourPID;
uint64_t _nextPeasantID{ 1 };
@@ -84,6 +96,70 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void _renameRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
void _handleQuitAll(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
// Method Description:
// - Helper for doing something on each and every peasant.
// - We'll try calling func on every peasant.
// - If the return type of func is void, it will perform it for all peasants.
// - If the return type is a boolean, we'll break out of the loop once func
// returns false.
// - If any single peasant is dead, then we'll call onError and then add it to a
// list of peasants to clean up once the loop ends.
// Arguments:
// - func: The function to call on each peasant
// - onError: The function to call if a peasant is dead.
// Return Value:
// - <none>
template<typename F, typename T>
void _forEachPeasant(F&& func, T&& onError)
{
using Map = decltype(_peasants);
using R = std::invoke_result_t<F, Map::key_type, Map::mapped_type>;
static constexpr auto IsVoid = std::is_void_v<R>;
std::vector<uint64_t> peasantsToErase;
for (const auto& [id, p] : _peasants)
{
try
{
if constexpr (IsVoid)
{
func(id, p);
}
else
{
if (!func(id, p))
{
break;
}
}
}
catch (const winrt::hresult_error& exception)
{
onError(id);
if (exception.code() == 0x800706ba) // The RPC server is unavailable.
{
peasantsToErase.emplace_back(id);
}
else
{
LOG_CAUGHT_EXCEPTION();
throw;
}
}
}
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
}
friend class RemotingUnitTests::RemotingTests;
};
}

View File

@@ -28,18 +28,36 @@ namespace Microsoft.Terminal.Remoting
Boolean FoundMatch;
SummonWindowBehavior SummonBehavior;
Windows.Foundation.IReference<UInt64> WindowID;
}
struct PeasantInfo
{
UInt64 Id;
String Name;
String TabTitle;
};
[default_interface] runtimeclass Monarch {
Monarch();
UInt64 GetPID();
UInt64 AddPeasant(IPeasant peasant);
UInt64 GetNumberOfPeasants();
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
void HandleActivatePeasant(WindowActivatedArgs args);
void SummonWindow(SummonWindowSelectionArgs args);
void SignalClose(UInt64 peasantId);
void SummonAllWindows();
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos { get; };
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
};
}

View File

@@ -20,8 +20,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
}
// This is a private constructor to be used in unit tests, where we don't
// want each Peasant to necessarily use the current PID.
// This constructor is intended to be used in unit tests,
// but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Peasant::Peasant(const uint64_t testPID) :
_ourPID{ testPID }
{
@@ -31,6 +33,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
_id = id;
}
uint64_t Peasant::GetID()
{
return _id;
@@ -222,4 +225,68 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestShowTrayIcon()
{
try
{
_ShowTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestShowTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestHideTrayIcon()
{
try
{
_HideTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestHideTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestQuitAll()
{
try
{
_QuitAllRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestQuit",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::Quit()
{
try
{
_QuitRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_Quit",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}

View File

@@ -28,11 +28,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void RequestIdentifyWindows();
void DisplayWindowId();
void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
void RequestQuitAll();
void Quit();
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs);
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs);
@@ -40,6 +45,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs);
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
Peasant(const uint64_t testPID);

View File

@@ -61,9 +61,14 @@ namespace Microsoft.Terminal.Remoting
void DisplayWindowId(); // Tells us to display its own ID (which causes a DisplayWindowIdRequested to be raised)
String WindowName { get; };
String ActiveTabTitle { get; };
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
void RequestQuitAll();
void Quit();
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
@@ -71,6 +76,10 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
};
[default_interface] runtimeclass Peasant : IPeasant

View File

@@ -34,6 +34,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
WINRT_PROPERTY(bool, FoundMatch, false);
WINRT_PROPERTY(bool, OnCurrentDesktop, false);
WINRT_PROPERTY(SummonWindowBehavior, SummonBehavior);
WINRT_PROPERTY(Windows::Foundation::IReference<uint64_t>, WindowID);
};
}

View File

@@ -54,6 +54,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// monarch!
CoRevokeClassObject(_registrationHostClass);
_registrationHostClass = 0;
SignalClose();
_monarchWaitInterrupt.SetEvent();
// A thread is joinable once it's been started. Basically this just
@@ -64,6 +65,18 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}
void WindowManager::SignalClose()
{
if (_monarch)
{
try
{
_monarch.SignalClose(_peasant.GetID());
}
CATCH_LOG()
}
}
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
{
// If we're the king, we _definitely_ want to process the arguments, we were
@@ -250,10 +263,15 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// Here, we're the king!
//
// This is where you should do any additional setup that might need to be
// done when we become the king. THis will be called both for the first
// done when we become the king. This will be called both for the first
// window, and when the current monarch dies.
_monarch.WindowCreated({ get_weak(), &WindowManager::_WindowCreatedHandlers });
_monarch.WindowClosed({ get_weak(), &WindowManager::_WindowClosedHandlers });
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
_monarch.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
_monarch.QuitAllRequested([this](auto&&, auto&&) { _QuitAllRequestedHandlers(*this, nullptr); });
_BecameMonarchHandlers(*this, nullptr);
}
@@ -509,4 +527,79 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_monarch.SummonWindow(args);
}
void WindowManager::SummonAllWindows()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
_monarch.SummonAllWindows();
}
}
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> WindowManager::GetPeasantInfos()
{
// We should only get called when we're the monarch since the monarch
// is the only one that knows about all peasants.
return _monarch.GetPeasantInfos();
}
uint64_t WindowManager::GetNumberOfPeasants()
{
if (_monarch)
{
try
{
return _monarch.GetNumberOfPeasants();
}
CATCH_LOG()
}
return 0;
}
// Method Description:
// - Ask the monarch to show a tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestShowTrayIcon()
{
co_await winrt::resume_background();
_peasant.RequestShowTrayIcon();
}
// Method Description:
// - Ask the monarch to hide its tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestHideTrayIcon()
{
auto strongThis{ get_strong() };
co_await winrt::resume_background();
_peasant.RequestHideTrayIcon();
}
// Method Description:
// - Ask the monarch to quit all windows.
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestQuitAll()
{
auto strongThis{ get_strong() };
co_await winrt::resume_background();
_peasant.RequestQuitAll();
}
bool WindowManager::DoesQuakeWindowExist()
{
return _monarch.DoesQuakeWindowExist();
}
void WindowManager::UpdateActiveTabTitle(winrt::hstring title)
{
winrt::get_self<implementation::Peasant>(_peasant)->ActiveTabTitle(title);
}
}

View File

@@ -39,9 +39,25 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
bool IsMonarch();
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SignalClose();
void SummonAllWindows();
uint64_t GetNumberOfPeasants();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
winrt::fire_and_forget RequestShowTrayIcon();
winrt::fire_and_forget RequestHideTrayIcon();
winrt::fire_and_forget RequestQuitAll();
bool DoesQuakeWindowExist();
void UpdateActiveTabTitle(winrt::hstring title);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
bool _shouldCreateWindow{ false };

View File

@@ -8,11 +8,25 @@ namespace Microsoft.Terminal.Remoting
{
WindowManager();
void ProposeCommandline(CommandlineArgs args);
void SignalClose();
Boolean ShouldCreateWindow { get; };
IPeasant CurrentWindow();
Boolean IsMonarch { get; };
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
UInt64 GetNumberOfPeasants();
void RequestQuitAll();
void UpdateActiveTabTitle(String title);
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
};
}

View File

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

View File

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

View File

@@ -45,7 +45,7 @@
Color="{ThemeResource SystemErrorTextColor}" />
<!-- Suppress top padding -->
<Thickness x:Key="TabViewHeaderPadding">8,0,8,0</Thickness>
<Thickness x:Key="TabViewHeaderPadding">9,0,8,0</Thickness>
<!-- Remove when implementing WinUI 2.6 -->
<Thickness x:Key="FlyoutContentPadding">12</Thickness>
@@ -55,12 +55,18 @@
<!-- Define resources for Dark mode here -->
<SolidColorBrush x:Key="TabViewBackground"
Color="#FF333333" />
<SolidColorBrush x:Key="UnfocusedBorderBrush"
Color="#FF333333" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- Define resources for Light mode here -->
<SolidColorBrush x:Key="TabViewBackground"
Color="#FFCCCCCC" />
<SolidColorBrush x:Key="UnfocusedBorderBrush"
Color="#FFCCCCCC" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>

View File

@@ -78,7 +78,14 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleCloseWindow(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
CloseWindow();
CloseWindow(false);
args.Handled(true);
}
void TerminalPage::_HandleQuit(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
RequestQuit();
args.Handled(true);
}
@@ -143,6 +150,20 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args == nullptr)
{
args.Handled(false);
}
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
{
auto moved = _MovePane(realArgs.TabIndex());
args.Handled(moved);
}
}
void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
@@ -161,6 +182,13 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleToggleSplitOrientation(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
_ToggleSplitOrientation();
args.Handled(true);
}
void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
@@ -257,12 +285,12 @@ namespace winrt::TerminalApp::implementation
{
if (args == nullptr)
{
_OpenNewTab(nullptr);
LOG_IF_FAILED(_OpenNewTab(nullptr));
args.Handled(true);
}
else if (const auto& realArgs = args.ActionArgs().try_as<NewTabArgs>())
{
_OpenNewTab(realArgs.TerminalArgs());
LOG_IF_FAILED(_OpenNewTab(realArgs.TerminalArgs()));
args.Handled(true);
}
}
@@ -307,8 +335,29 @@ namespace winrt::TerminalApp::implementation
}
else
{
_MoveFocus(realArgs.FocusDirection());
args.Handled(true);
// Mark as handled only when the move succeeded (e.g. when there
// is a pane to move to), otherwise mark as unhandled so the
// keychord can propagate to the terminal (GH#6129)
const auto moveSucceeded = _MoveFocus(realArgs.FocusDirection());
args.Handled(moveSucceeded);
}
}
}
void TerminalPage::_HandleSwapPane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& realArgs = args.ActionArgs().try_as<SwapPaneArgs>())
{
if (realArgs.Direction() == FocusDirection::None)
{
// Do nothing
args.Handled(false);
}
else
{
auto swapped = _SwapPane(realArgs.Direction());
args.Handled(swapped);
}
}
}
@@ -711,11 +760,10 @@ namespace winrt::TerminalApp::implementation
newTerminalArgs = NewTerminalArgs();
}
const auto profileGuid{ _settings.GetProfileForArgs(newTerminalArgs) };
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
// Manually fill in the evaluated profile.
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profileGuid));
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
_OpenNewWindow(false, newTerminalArgs);
actionArgs.Handled(true);
}
@@ -833,4 +881,44 @@ namespace winrt::TerminalApp::implementation
}
}
}
void TerminalPage::_HandleOpenSystemMenu(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
_OpenSystemMenuHandlers(*this, nullptr);
args.Handled(true);
}
void TerminalPage::_HandleClearBuffer(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<ClearBufferArgs>())
{
if (const auto termControl{ _GetActiveControl() })
{
termControl.ClearBuffer(realArgs.Clear());
args.Handled(true);
}
}
}
}
void TerminalPage::_HandleMultipleActions(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<MultipleActionsArgs>())
{
for (const auto& action : realArgs.Actions())
{
_actionDispatch->DoAction(action);
}
args.Handled(true);
}
}
}
}

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