Compare commits

..

154 Commits

Author SHA1 Message Date
Dustin L. Howett
0dc02fe1a0 Migrate spelling-0.0.21 changes from main 2020-10-20 16:39:36 -07:00
Dustin L. Howett
2171d77855 Migrate spelling-0.0.19 changes from main 2020-10-20 16:39:36 -07:00
Michael Niksa
c1ca8f346d Okay so this will have it convert correctly, but the caller isn't allocating enough memory to get this back now. So we have to look a layer up. 2020-10-20 16:39:36 -07:00
Ryuichi Ito
743283e434 Fix garbling when copying multibyte text via OSC 52 (#7870)
This commit adds a missing conversion utf8 to utf16 in decoding base64
for handling multibyte text in copying via OSC 52.

## Validation Steps Performed
* automatically
    * Tests w/ multibyte characters
* manually
    * case1
        * Executed `printf "\x1b]52;;%s\x1b\\" "$(printf '👍👍🏻👍🏼👍🏽👍🏾👍🏿' | base64)"`
        * Verified `👍👍🏻👍🏼👍🏽👍🏾👍🏿` in my clipboard
    * case2
        * Copied `👍👍🏻👍🏼👍🏽👍🏾👍🏿` by tmux 2.6 default copy function (OSC 52)
        * Verified `👍👍🏻👍🏼👍🏽👍🏾👍🏿` in my clipboard

Closes #7819
2020-10-16 02:02:59 +00:00
Chester Liu
4a4a41eadf Optimize the binary size of the XOrg color table (#7929)
This optimizes the binary size of the xorg color table by replacing the
static lookup table with a table of variable colors (indexed "" (0)
through "4"), calculated greys for gr[ae]y0-100, and a table of the
remaining unsuffixed colors.

78 variable colors ...
  8 bytes each for pointer+size
  5 variants, 4 bytes each for the color data
718 bytes for 0-terminated color names

plus

84 colors ...
  8 bytes each for pointer+size
  4 bytes each for the color data
955 bytes for 8-terminated color names

  2902 = (78 * 8) + (78 * 5 * 4) + 718
+ 1963 = (84 * 8) + (84   *   4) + 955
------
  4865 bytes (approximately)

"I couldn't sleep at night thinking that after years of accusing Windows
being bloated and literally making it even more bloated with my hands.
So here you go. The mediocre yet working solution. This reduces the
binary size to 1051k (1067k before) while keeping the code maintainable
for human beings."
2020-10-15 17:45:33 -07:00
Don-Vito
60d681d564 7395: do not clear text selection upon PrintScreen (#7883)
When handling SendKey, preserve selection upon PrintScreen (VK_SNAPSHOT)

Closes #7395
2020-10-15 17:01:01 -07:00
Mike Griese
bd7cd5512d Swap the command palette modes for the prefix > (#7935)
VsCode uses `>` as its "prefix" for the equivalent of their "action
mode". This PR aligns the Terminal with their logic here. 

We have to be tricky - if we use the `>` in the actual input as the
indicator for action mode, we can't display any placeholder text in the
input to tell users to type a command. This wasn't an issue for the
commandline mode previously, because we'd stick the "prompt" in the "no
matches text" space. However, we can't do that for action mode. Instead,
we'll stick a floating text block over the input box, and when the
user's in action mode, we'll manually place a `>` into that space. When
the user backspaces the `>`, we'll remove it from that block, and switch
into commandline mode.

## Validation Steps Performed
Played with the cmdpal in lots of different modes, this finally feels
good

Closes #7736
2020-10-15 15:58:35 -07:00
James Holderness
30e363e7ac Add support for the DECREQTPARM report (#7939)
This PR adds support for the `DECREQTPARM` (Request Terminal Parameters)
escape sequence, which was originally used on the VT100 terminal to
report the serial communication parameters. Modern terminal emulators
simply hardcode the reported values for backward compatibility.

The `DECREQTPARM` sequence has one parameter, which was originally used
to tell the terminal whether it was permitted to send unsolicited
reports or not. However, since we have no reason to send an unsolicited
report, we don't need to keep track of that state, but the permission
parameter does still determine the value of the first parameter in the
response.

The response parameters are as follows:

| Parameter        | Value  | Meaning                  |
| ---------------- | ------ | ------------------------ |
| response type    | 2 or 3 | unsolicited or solicited |
| parity           | 1      | no parity                |
| data bits        | 1      | 8 bits per character     |
| transmit speed   | 128    | 38400 baud               |
| receive speed    | 128    | 38400 baud               |
| clock multiplier | 1      |                          |
| flags            | 0      |                          |

There is some variation in the baud rate reported by modern terminal
emulators, and 9600 baud seems to be a little more common than 38400
baud, but I thought the higher speed was probably more appropriate,
especially since that's also the value reported by XTerm.

## Validation Steps Performed

I've added a couple of adapter and output engine tests to verify that
the sequence is dispatched correctly, and the expected responses are
generated. I've also manually tested in Vttest and confirmed that we now
pass the `DECREQTPARM` test in the _Test of terminal reports_.

Closes #7852
2020-10-15 15:50:02 -07:00
Mike Griese
9d911c01fb Increase contrast ratio on the CmdPal shortcut text (#7937)
Related to #7915.
2020-10-15 15:49:20 -07:00
Mike Griese
98806e27b1 Add a setting to configure the audible bell (#7793)
Adds a new setting, `bellStyle`, to be able to disable the audible bell
added in #7679. Currently, this setting accepts two values:
* `audible`: play a noise on a bell
* `none`: Don't play a noise.

In the future, we can add a `"bellStyle": "visible"` for flashing the
Terminal instead of making a noise on bell.

## Validation Steps Performed
Pressing <kbd>Ctrl+G</kbd> in cmd, and hitting enter is an easy way of
triggering a bell. I set the setting to `none`, and presto, the bell
stopped.

Closes #2360
2020-10-15 15:27:27 -07:00
Dustin L. Howett
e996fadbb6 conpty: fall back to conhost if OpenConsole is missing (#7741)
This commit is in support of WTU.

I initially added support for a new flag, `PSEUDOCONSOLE_UNDOCKED_PREFER_INBOX_CONHOST`,
which I liked because it was more explicit. We chose not to go that route.

### Automatic fallback
#### Pros
* It's easier on the consumer
* We can eventually expand it to support `$ARCH/openconsole.exe`
#### Cons
* Packaging the project wrong will result in a working-but-somewhat-broken experience (old conhost)
   * We ameliorated this by checking it in the packaging script.
* Implicit behavior may be bad
2020-10-15 11:50:27 -07:00
Mike Griese
5662cc1710 doc: Remove unnecessary link to VC redist, update md lint rules (#7926)
Terminal ships with this dependency embedded, and it is not required that you install it separately. Since the link is broken, let's just remove it entirely.

* [x] fixes #7889 
* [x] related to https://github.com/microsoft/terminal/issues/7917#issuecomment-707955335
* [x] I work here
* [x] is a docs update

Additionally, update the markdown linter rules in the wake of #7637, because apparently that was never actually applied to any files, so now the onus is on the first person to touch any of our markdown files.
2020-10-15 11:49:11 -07:00
James Holderness
55151a4a04 Refactor VT parameter handling (#7799)
This PR introduces a pair of classes for managing VT parameters that
automatically handle range checking and default fallback values, so the
individual operations don't have to do that validation themselves. In
addition to simplifying the code, this fixes a few cases where we were
mishandling missing or extraneous parameters, and adds support for
parameter sequences on commands that couldn't previously handle them.
This PR also sets a limit on the number of parameters allowed, to help
thwart DoS memory consumption attacks.

## References

* The new parameter class also introduces the concept of an
  omitted/default parameter which is not necessarily zero, which is a
  prerequisite for addressing issue #4417.

## Detailed Description of the Pull Request / Additional comments

There are two new classes provide by this PR: a `VTParameter` class,
similar in function to a `std::optional<size_t>`, which holds an
individual parameter (which may be an omitted/default value); and a
`VTParameters` class, similar in function to `gsl:span<VTParameter>`,
which holds a sequence of those parameters.

Where `VTParameter` differs from `std::optional` is with the inclusion
of two cast operators. There is a `size_t` cast that interprets omitted
and zero values as 1 (the expected behaviour for most numeric
parameters). And there is a generic cast, for use with the enum
parameter types, which interprets omitted values as 0 (the expected
behaviour for most selective parameters).

The advantage of `VTParameters` class is that it has an `at` method that
can never fail - out of range values simply return the a default
`VTParameter` instance (this is standard behaviour in VT terminals). It
also has a `size` method that will always return a minimum count of 1,
since an empty parameter list is typically the equivalent of a single
"default" parameter, so this guarantees you'll get at least one value
when iterating over the list with `size()`.

For cases where we just need to call the same dispatch method for every
parameter, there is a helper `for_each` method, which repeatedly calls a
given predicate function with each value in the sequence. It also
collates the returned success values to determine the overall result of
the sequence. As with the `size` method, this will always make at least
one call, so it correctly handles empty sequences.

With those two classes in place, we could get rid of all the parameter
validation and default handling code in the `OutputStateMachineEngine`.
We now just use the `VTParameters::at` method to grab a parameter and
typically pass it straight to the appropriate dispatch method, letting
the cast operators automatically handle the assignment of default
values. Occasionally we might need a `value_or` call to specify a
non-standard default value, but those cases are fairly rare.

In some case the `OutputStateMachineEngine` was also checking whether
parameters values were in range, but for the most part this shouldn't
have been necessary, since that is something the dispatch classes would
already have been doing themselves (in the few cases that they weren't,
I've now updated them to do so).

I've also updated the `InputStateMachineEngine` in a similar way to the
`OutputStateMachineEngine`, getting rid of a few of the parameter
extraction methods, and simplifying other parts of the implementation.
It's not as clean a replacement as the output engine, but there are
still benefits in using the new classes.

## Validation Steps Performed

For the most part I haven't had to alter existing tests other than
accounting for changes to the API. There were a couple of tests I needed
to drop because they were checking for failure cases which shouldn't
have been failing (unexpected parameters should never be an error), or
testing output engine validation that is no longer handled at that
level.

I've added a few new tests to cover operations that take sequences of
selective parameters (`ED`, `EL`, `TBC`, `SM`, and `RM`). And I've
extended the cursor movement tests to make sure those operations can
handle extraneous parameters that weren't expected. I've also added a
test to verify that the state machine will correctly ignore parameters
beyond the maximum 32 parameter count limit.

I've also manual confirmed that the various test cases given in issues
#2101 are now working as expected.

Closes #2101
2020-10-15 16:12:52 +00:00
Nicholas Bennett
6e8388e683 Auto detect background image (#7849)
##  Summary of the Pull Request
Added watch on desktopImagePath to check when the path equals "DesktopWallpaper"
If it does equal "DesktopWallpaper" it replaces the path with a path to the desktop's wallpaper

*I am a student and this is my first pull request for Terminal so please give feedback no matter how small. It's the best way I can learn.

## PR Checklist
* [X] Closes #7295 
* [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: https://github.com/MicrosoftDocs/terminal/pull/155
* [?] Schema updated. (Not sure if this is needed, also not sure where this would be)
* [X] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #7295 (Have only talked with the people on the issue, which I don't think has any core contributors)

## Detailed Description of the Pull Request / Additional comments
I am using SystemParametersInfo for SPI_GETDESKWALLPAPER which puts the path into a WCHAR and that is then inserted as the BackgroundImagePath.

I do not think an additional test would add value. The SPI_GETDESKTOPWALLPAPER uses the computers local wallpaper path and puts it into a WCHAR, which then I feed into BackgroundImagePath() as it's new path. I don't think there adds value in making a static path of the desktop background and testing that, given that static tests are already done for "BackgroundImage()".

## Validation Steps Performed

(Manual Validation - Test False Value)
1. Ran Terminal
2. Set setting ["backgroundImage": "<some random img path>"] under profiles->defaults
3. Verified terminal's background is not the desktops wallpaper. 

(Manual Validation - Test True Value)
1. Ran Terminal
2. Set setting ["backgroundImage": "DesktopWallpaper"] under profiles->defaults
3. Verified the background image matches the desktop background image. 

(Manual Validation - Multiple Tabs True Value)
1. Ran Terminal
2. Set setting ["backgroundImage": "DesktopWallpaper"] under profiles->defaults
3. Verified the background image matches the desktop background image.  
4. Opened new tabs
5. Verified the background image matches the desktop background image for each tab.
2020-10-15 16:09:20 +00:00
Mike Griese
8bdae31f6b Fix the linter for C++ files (#7930)
For whatever reason, the super linter seems to think that any file it doesn't recognize is an EDITORCONFIG file. That means all our `cpp`, `hpp`, `h`, `resw`, `xaml`, etc files are going to get linted with different rules than the clang-format ones we already use. 

This PR disables the EDITORCONFIG linter, and has a minimal change to a cpp file to ensure that it's no longer linted by the action.

See also: 
* #7637 added this
* #7799 is blocked by this
* #7924 is blocked by this
2020-10-15 16:05:54 +00:00
mpela81
004da88bba Add Close... option to Tab context menu (#7728)
<!-- 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 "Close..." option to the tab context menu, with nested entries to close tabs to the right and close other tabs (actions already available)
![immagine](https://user-images.githubusercontent.com/1140981/94178005-c7e03600-fe9a-11ea-9f87-c6f4895d4cf3.png)

<!-- 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 #5524
* [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
First contribution 🙂
Tried to follow some suggestions from https://github.com/microsoft/terminal/issues/1912#issuecomment-667079311

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2020-10-15 06:40:44 -05:00
Quang Kieu
f78687453c doc: Update Notice emoji (#7424)
Just a simple update on the emoji so that it catch people attention as I
see many open issues about terminal not running on Windows Server 2016.
2020-10-14 17:45:38 -07:00
WSLUser
afcc930119 Add Github Action Super Linter (#7637)
This uses the templates from
https://github.com/github/super-linter/tree/master/TEMPLATES currently.
A future PR can add the necessary templates to the Windows Terminal
repository and update the source of Templates following the README.
Additionally we can add flags to explicitly choose the linters
applicable to this code base but is not necessary.

Per the README, this does not enforce any linting rules but rather
outputs the suggestions in the build step, which are to be read by the
PR submitter and Windows Terminal team to determine if they want to use
the linting rule. C++ is currently not supported (Powershell, Json,
Yaml, and Markdown will be the only things the linter checks for
currently) but we could add our own custom support if desired in
separate PR.

## Validation Steps Performed
It successfully runs. Currently only shows the yaml file itself being
linted in this PR as a test case. It will apply to new PRs once this is
merged. We can lint existing code base but would require a separate PR
and examining the code output (also requires updating the yaml file
temporarily).

Closes #7513
2020-10-14 17:34:41 -07:00
Chester Liu
33321b8596 doc: address how to use TAEF (#7590)
This PR updates the doc in the repo to address how to use TAEF as part
of the development workflow.

Closes #1962
2020-10-14 17:33:12 -07:00
Chester Liu
02b120236c Add support for more OSC color formats (#7578)
* Correct the behaviour of parsing `rgb:R/G/B`. It should be interpreted
  as `RR/GG/BB` instead of `0R/0G/0B`
* Add support for `rgb:RRR/GGG/BBB` and `rgb:RRRR/GGGG/BBBB`. The
  behaviour of 12 bit variants is to repeat the first digit at the end,
  e.g. `rgb:123/456/789` becomes `rgb:1231/4564/7897`.
* Add support for `#` formats. We are following the rules of
  [XParseColor] by interpreting `#RGB` as `R000G000B000`.
* Add support for XOrg app color names, which are supported by xterm, VTE
  and many other terminal emulators.
* Multi-parameter OSC 4 is now supported.
* The chaining of OSC 10-12 is not yet supported. But the parameter
  validation is relaxed by parsing the parameters as multi-params but
  only use the first one, which means `\e]10;rgb:R/G/B;` and
  `\e]10:rgb:R/G/B;invalid` will execute `OSC 10` with the first color
  correctly. This fixes some of the issues mentioned in #942 but not
  all of them.

[XParseColor]: https://linux.die.net/man/3/xparsecolor

Closes #3715
2020-10-14 17:29:10 -07:00
Don-Vito
f86045e041 7124: Add focus and maximizedFocus launch modes (#7873)
This commit introduces two new launch modes: focus and maximizedFocus. 
* Focused mode, behaves like a default mode, but with the Focus Mode
  enabled.
* Maximized focused mode, behaves like a Maximized mode, but with the
  Focus Mode enabled.

There two ways to invoke these new modes:
* In the settings file: you set the "launchMode" to either "focus" or
  "maximizedFocus"
* In the command line options, you can path -f / --focus, which is
  mutually exclusive with the --fullscreen, but can be combined with the
  --maximized:
  * Passing -f / --focus will launch the terminal in the "focus" mode
  * Passing -fM / --focus --maximized will launch the terminal in the
    "maximizedFocus" mode

This should resolve a relevant part in the command line arguments
mega-thread #4632

Closes #7124
Closes #7825
Closes #7875
2020-10-14 22:19:51 +00:00
Don-Vito
cb732a4bcc 7571: do not activate terminal window upon settings modificaion (#7887)
Took this as an easy starter. The method IslandWindow::SetAlwaysOnTop is
triggered once terminal settings are reloaded (in
TerminalPage::_RefreshUIForSettingsReload flow). This method calls
SetWindowPos without SWP_NOACTIVATE. As a result the window gets
activated, the focus is set and the cursor starts blinking.

Added SWP_NOACTIVATE in all SetWindowPos calls from IslandWindow and
NoClientIslandWindow (where it was missing). Please let me know if this
is an overkill - it is not required to fix the issue, however seems a
good practice, that might help if we decide to apply more settings
immediately.

## Validation Steps Performed
* Only manual testing - please guide me to the relevant UT framework, if
  exists. 
* Trying to reproduce this with VS attached doesn't work - the window
  gets the focus in any case.
* Tested as a standalone application, by modifying different settings
  (and comparing the results before and after the fix).
* Checked with Spy++ that no WM_ACTIVATE / WM_SETFOCUS is thrown upon
  settings modification
* Applied terminal resizing, toggling full screen and focus mode to
  check no regression was introduced.

Closes #7571
2020-10-13 15:40:56 -07:00
Javier
d2d462fc48 wpf: fix margin calculations and resize events (#7892) 2020-10-12 18:21:11 -07:00
Kayla Cinnamon
cb96aa718f doc: add SUPPORT.md (#7902) 2020-10-12 17:15:52 -07:00
Kayla Cinnamon
9b203d40c1 Fix capitalization in hyperlink tooltip (#7901) 2020-10-12 17:15:25 -07:00
PankajBhojwani
8d12388915 Inform user that holding alt opens a new pane (#7866)
Adds a tooltip to the new tab button and menu  to let the user know
that holding alt will open a new pane instead.

Fixes #7851

Co-authored-by: Pankaj Bhojwani <pabhojwa@microsoft.com>
2020-10-12 17:14:02 -07:00
Mike Griese
9dc38ad0f5 Add an animation to pane entrance/exit (#7364)
Adds an entrance animation when panes are created. This animation can be
disabled with the `disableAnimations` global setting. 

Although the XAML animation documentation was pretty heavy on the _do it
in XAML_ route, our panes are created pretty much entirely in code, so
we've got to create the animations in code as well. 

200ms as the duration of the animation was picked _super_ arbitrarily.
300ms felt too long, and 166ms felt like it was only visible for a
single frame. 

see also:
* [Motion in practice](https://docs.microsoft.com/en-us/windows/uwp/design/motion/motion-in-practice)
* [This example](https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Media.Animation.Storyboard?view=winrt-19041#examples) what what I ended up using, albeit ported to cppwinrt.
* [`Timeline.AllowDependentAnimations`](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.media.animation.timeline.allowdependentanimations?view=winrt-19041#Windows_UI_Xaml_Media_Animation_Timeline_AllowDependentAnimations)
* [easing functions](https://docs.microsoft.com/en-us/windows/uwp/design/motion/key-frame-and-easing-function-animations#easing-functions)

## Validation Steps Performed
Man have I been opening panes

Closes #1001
Closes #7366
2020-10-09 23:06:40 +00:00
Javier
9e86e29584 wpf: Add AutoFill to control whether the connection/buffer resizes (#7853)
Adds the ability to manually handle the terminal renderer resizing
events by allowing different render size and WPF control size. This is
done by adding an `AutoFill` property to the control that prevents the
renderer from automatically resizing and tells the WPF control to fill
in the extra space with the terminal background as shown below:

This PR adds the following:
- Helper method in the DX engine to convert character viewports into
  pixel viewports
- `AutoFill` property that prevents automatic resizing of the renderer
- Tweaks and fixes that automatically fill in the empty space if
  `AutoFill` is set to false
- Fixes resizing methods and streamlines their codepath

## Validation Steps Performed
Manual validation with the Visual Studio Integrated Terminal tool
window.
2020-10-09 22:25:18 +00:00
Dustin L. Howett
cd768934be Hook up the WIL fallback error tracer in Terminal (#7864)
This pull request introduces (a very, very stripped-down copy of) the
WIL fallback error reporter.

It emits error records, usually immediately before the application
implodes, into the event stream.

This should improve diagnosability of issues that take Terminal down,
and allow us to give out a .wprp file to gather traces from users.
2020-10-09 22:20:12 +00:00
Carlos Zamora
7a1932c556 Fix UIA ScrollIntoView at EndExclusive (#7868)
`ScrollIntoView` is responsible for scrolling the viewport to include
the UTR's start endpoint. The crash was caused by `start` being at the
exclusive end, and attempting to scroll to it. This is now fixed by
clamping the result to the bottom of the buffer.

Most of the work here is to allow a test for this. `ScrollIntoView`
relied on a virtual `ChangeViewport` function. By making that
non-virtual, the `DummyElementProvider` in the tests can now be a
`ScreenInfoUiaProviderBase`. This opens up the possibility of more
UiaTextRange tests in the future too.

Closes #7839
2020-10-09 20:27:13 +00:00
Dustin L. Howett
d33ca7e8eb From orbit, nuke the Telnet connection and all supporting infra. (#7840)
This is not going to be our plan of record for Universal going forward.

This updates the Universal configuration to 1) match non-universal and 2) switch to local applications
2020-10-09 18:59:58 +00:00
Carlos Zamora
f0b8875770 Add Spec for winrt TerminalSettings (#6904)
## Summary of the Pull Request
This introduces a spec for (what I like to call) winrt TerminalSettings. Basically, we need to move over some of the code that resides in TerminalApp that relates to the settings model, then expose some of the settings objects as winrt objects. Doing so will allow us to access/modify settings across different project layers (a must-have for the Settings UI).

## References
#885 - winrt Terminal Settings issue
#1564 - spec for most of the backend work for Settings UI
2020-10-09 11:19:40 -07:00
Dustin Howett
91ccbb79f0 BUILD: Disable parallel build
The build agents can't handle the size of our PCH files.

Signed-off-by: Dustin Howett <duhowett@microsoft.com>
2020-10-08 17:17:55 -07:00
Carlos Zamora
4fc607a44d Introduce IconConverter (#7830)
## Summary of the Pull Request
Introduce the `IconPathConverter` to `TerminalApp`. `Command` and `Profile` now both return the unexpanded icon path. `IconPathConverter` is responsible for expanding the icon path and retrieving the appropriate icon source.

This also removes `Profile`'s expanded icon path and uses the `IconPathConverter` when necessary. This allows users to set profile icons to emoji as well. However, emoji do not appear in the jumplist.

## References
Based on #7667 

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

## Validation Steps Performed
Deploy succeeded.
2020-10-08 11:29:04 -07:00
Carlos Zamora
ba79d53887 Fix old reference to TermApp::Command (#7863)
#7796 and #7667 were being implemented concurrently. As a part of #7667, Command was moved from TermApp to TSM. This just applies that change to a line we missed in #7796 and fixes the build break.
2020-10-08 10:42:45 -07:00
Mike Griese
22887d721f Preview tab switching with the ATS (#7796)
## Summary of the Pull Request

![preview-ats-000](https://user-images.githubusercontent.com/18356694/94801728-18302a00-03ac-11eb-851d-760b92ebb46f.gif)

This PR enables the ATS to display the active tab as the user navigates the tab switcher. We do this by dispatching the tab switch actions as the user navigates the menu, and manually _not_ focusing the new tab when the tab switcher is open. 

## References

* #6732 - original tab switcher PR
* #6689 - That's a more involved, generic version of this, but this PR will be enough to stop most of the complaints hopefully


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

## Validation Steps Performed

Opened tabs, tabbed through the menu, verified that it did what I'd expect
2020-10-07 22:49:10 +00:00
Carlos Zamora
2608e94822 Introduce TerminalSettingsModel project (#7667)
Introduces a new TerminalSettingsModel (TSM) project. This project is
responsible for (de)serializing and exposing Windows Terminal's settings
as WinRT objects.

## References
#885: TSM epic
#1564: Settings UI is dependent on this for data binding and settings access
#6904: TSM Spec

In the process of ripping out TSM from TerminalApp, a few other changes
were made to make this possible:
1. AppLogic's `ApplicationDisplayName` and `ApplicationVersion` was
   moved to `CascadiaSettings`
   - These are defined as static functions. They also no longer check if
     `AppLogic::Current()` is nullptr.
2. `enum LaunchMode` was moved from TerminalApp to TSM
3. `AzureConnectionType` and `TelnetConnectionType` were moved from the
   profile generators to their respective TerminalConnections
4. CascadiaSettings' `SettingsPath` and `DefaultSettingsPath` are
   exposed as `hstring` instead of `std::filesystem::path`
5. `Command::ExpandCommands()` was exposed via the IDL
   - This required some of the warnings to be saved to an `IVector`
     instead of `std::vector`, among some other small changes.
6. The localization resources had to be split into two halves.
   - Resource file linked in init.cpp. Verified at runtime thanks to the
     StaticResourceLoader.
7. Added constructors to some `ActionArgs`
8. Utils.h/cpp were moved to `cascadia/inc`. `JsonKey()` was moved to
   `JsonUtils`. Both TermApp and TSM need access to Utils.h/cpp.

A large amount of work includes moving to the new namespace
(`TerminalApp` --> `Microsoft::Terminal::Settings::Model`).

Fixing the tests had its own complications. Testing required us to split
up TSM into a DLL and LIB, similar to TermApp. Discussion on creating a
non-local test variant can be found in #7743.

Closes #885
2020-10-06 09:56:59 -07:00
Carlos Zamora
e401edf9ef Properly handle and test a11y movement at end of buffer (#7792)
The `MovementAtExclusiveEnd` test was improperly authored for the
following reasons:
- it should have used `TEST_METHOD_PROPERTY` to cover all of the
  TextUnits
- TextUnit::Document (arguably one of the most important) was ommitted
  accidentally (`!= TextUnit_Document` was used instead of `<=`)
- The created range was not `EndExclusive`, but rather, the last cell in
  the buffer (`EndInclusive`)

The first half of this PR fixes the test.

The second half of this PR expands the test and fixes any related issues
to make the test pass (i.e. #7771):
- `TEST_METHOD_PROPERTY` was added for it to be degenerate (start/end at
  `EndExclusive`) or not (last cell of buffer)
- `utr->_start` is now also validated after moving backwards

NOTE: `utr->_start` was not validated when moving forwards because
moving forwards should always fail when at/past the last chell in the
buffer.

Closes #7771
2020-10-05 15:11:47 -07:00
Kayla Cinnamon
4a114971f9 doc: Update roadmap with latest info (#7778) 2020-09-30 20:14:45 -07:00
James Holderness
09cc5f492c Add support for the BEL control in Windows Terminal (#7679)
This commit makes the Windows Terminal play an audible sound when the
`BEL` control character is output.

The `BEL` control was already being forwarded through conpty, so it was
just a matter of hooking up the `WarningBell` dispatch method to
actually play a sound. I've used the `PlaySound` API to output the sound
configured for the "Critical Stop" system event (aka _SystemHand_),
since that is the sound used in conhost.

## Validation

I've manually confirmed that the terminal produces the expected sound
when executing `echo ^G` in a cmd shell, or `printf "\a"` in a WSL bash
shell.

References:
* There is a separate issue (#1608) to deal with configuring the `BEL`
  to trigger visual forms of notification.
* There is also an issue (#2360) requesting an option to disable the
  `BEL`.

Closes #4046
2020-09-30 18:00:06 -07:00
Carlos Zamora
386ae04edf Add optimization to get a11y next word (#7789)
This performs a minor refactor on `TextBuffer::MoveToNextWord` that
relies more heavily on `TextBuffer::GetWordEnd`. Now, the logic is
simplified and looks more like `MoveToPreviousWord`.

This refactor required me to move the `lastCharPos` optimization down to
`GetWordEnd`. So word expansion gets this optimization for free now.

### WPR Traces
The percentages below represent the weight that a function call had. The
test scenario included moving by word on the CMD welcome message until
the last word was reached. Inspect.exe was used to limit any additional
calls that are generally performed by a screen reader.

| function   | current | branch |
| --         | --      | --     |
| `UIA:Move` | 34.55%  | 29.52% |

There is an improvement of about 5% in a release build of ConHost.

NOTE: `UIA::Move` already calls `Expand` after a move operation is
performed. I'm using this data to represent a performance improvement
across both functions.

Contributes to #5243
2020-09-30 21:11:46 +00:00
Dustin L. Howett
da4ca86680 Fix parallel builds by specifying the application type for WAP (#7783)
The WAP packaging project is sensitive to including applications that it
thinks are UWPs. The changes we made to separate WindowsStoreApp and
WindowsAppContainer weren't comprehensive enough to convince WAP that we
were not still UWPs.

Because of that, it would run sub-builds of each of these projects (and
all their dependencies) with an additional `GenerateAppxPackageOnBuild`
property set. The existence of this property caused MSBuild to think the
projects needed to be built *again*.
2020-09-30 13:25:50 -07:00
Carlos Zamora
9ec57a7d3c Fix and test TextBuffer::MoveToPreviousWord() (#7770)
This fixes a bug when moving backwards by word that resulted in #7742.

This also includes...
- a minor refactor that leverages `GetWordStart` in `MoveToPreviousWord`
- additional unit tests for movement by word
- a feature test comprised of the referenced bug report

`MoveToPreviousWord()` would...
- move backwards for each whitespace character
- then, move backwards for each regular character

This would actually result in moving to the beginning of the current "word" (as defined by a11y).

We actually need to do this process twice:
- the first time gets you to the beginning of the current word
- attempt to move back by one character
- the second time gets you to the beginning of the previous word

Rather than implementing 4 while loops, we leverage `GetWordStart()` to
attempt to move to the beginning of the previous word. We call it twice
(as described above). The logic is unchanged, but we instead reuse a
function that has already undergone more testing.

To make sure this works as expected, additional unit tests were
introduced covering "MoveByWord" in the TextBuffer.

## Validation Steps Performed
Added test for repro steps.
Added unit tests for movement by word.

Closes #7742
2020-09-30 18:13:22 +00:00
Dustin L. Howett
6f051140da Introduce til::presorted_static_map (#7640)
til::static_map can't be constexpr until we move to C++20.
It can't be constexpr because std::sort isn't constexpr until then.
This poses a problem: if we start using it and treating it like a map,
we'll incur a potentially high cost in static initialization in both
code size in .text and runtime.

This commit introduces presorted_static_map, which is static_map except
that it doesn't automatically sort its keys. That's the only difference.

At this point, it's just a maplike interface to a constant array of
pairs that does a binary search. It should be used for small tables that
are used infrequently enough as to not warrant their cost in code size
or initialization time. It should also be used for tables that aren't
going to be edited much by developers (like the color table in #7578.)
2020-09-29 19:01:50 +00:00
PankajBhojwani
3cf31fbde4 Fix the "visual representation" optimization for hyperlinks (#7738)
Closes #7700
2020-09-28 23:49:25 +00:00
Dustin L. Howett
c3b3f5f0ba When win32 is resizing the viewport, make sure Right > Left (#7768)
Sometimes when we were sliding the viewport to fit inside the buffer, we
would end up with left > right.

That would cause us to crash down the line when rendering.

Fixes MSFT:28387423
Fixes #7744
2020-09-28 15:46:45 -07:00
Dustin L. Howett
f28ec65843 Normalize file paths before handing them to the jumplist (#7711)
DestListLogoUri cannot handle paths that are separated with / unless
they're actually URLs. We have to guess somewhat whether something is a
file path and if it appears to be one, normalize it.

Fixes #7706
2020-09-23 16:30:57 -07:00
Carlos Zamora
40893b2823 Fix A11y EndExclusive Error for Move & Expand (#7677)
`EndExclusive` represents the end of the buffer. This is designed to not
point to any data on the buffer. UiaTextRange would point to this
`EndExclusive` and then attempt to move based on it. However, since it
does not point to any data, it could experience undefined behavior or
(inevitably) crash from running out of bounds.

This PR specifically checks for expansion and movement at that point,
and prevents us from moving beyond it. There are plans in the future to
define the "end" as the last character in the buffer. Until then, this
solution will suffice and provide correct behavior that doesn't crash.

## Validation Steps Performed
Performed the referenced bugs' repro steps and added test coverage.

Closes MSFT-20458595
Closes #7663
Closes #7664
2020-09-23 20:06:18 +00:00
Leon Liang
9539ec3679 Add docs for creating a new DLL (#7661)
This PR adds a docs page for the gotchas and things to do when creating a new WinRT dll project.
2020-09-22 15:52:57 -07:00
Dustin Howett
49b9d41caf version: bump to 1.5 on master
Signed-off-by: Dustin Howett <duhowett@microsoft.com>
2020-09-22 08:19:08 -07:00
James Holderness
d1671a0acd Add support for the "blink" graphic rendition attribute (#7490)
This PR adds support for the _blink_ graphic rendition attribute. When a
character is output with this attribute set, it "blinks" at a regular
interval, by cycling its color between the normal rendition and a dimmer
shade of that color.

The majority of the blinking mechanism is encapsulated in a new
`BlinkingState` class, which is shared between the Terminal and Conhost
implementations. This class keeps track of the position in the blinking
cycle, which determines whether characters are rendered as normal or
faint. 

In Windows Terminal, the state is stored in the `Terminal` class, and in
Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases,
the `IsBlinkingFaint` method is used to determine the current blinking
rendition, and that is passed on as a parameter to the
`TextAttribute::CalculateRgbColors` method when these classes are
looking up attribute colors.

Prior to calculating the colors, the current attribute is also passed to
the `RecordBlinkingUsage` method, which keeps track of whether there are
actually any blink attributes in use. This is used to determine whether
the screen needs to be refreshed when the blinking cycle toggles between
the normal and faint renditions.

The refresh itself is handled by the `ToggleBlinkingRendition` method,
which is triggered by a timer. In Conhost this is just piggybacking on
the existing cursor blink timer, but in Windows Terminal it needs to
have its own separate timer, since the cursor timer is reset whenever a
key is pressed, which is not something we want for attribute blinking.

Although the `ToggleBlinkingRendition` is called at the same rate as the
cursor blinking, we actually only want the cells to blink at half that
frequency. We thus have a counter that cycles through four phases, and
blinking is rendered as faint for two of those four. Then every two
cycles - when the state changes - a redraw is triggered, but only if
there are actually blinking attributes in use (as previously recorded).

As mentioned earlier, the blinking frequency is based on the cursor
blink rate, so that means it'll automatically be disabled if a user has
set their cursor blink rate to none. It can also be disabled by turning
off the _Show animations in Windows_ option. In Conhost these settings
take effect immediately, but in Windows Terminal they only apply when a
new tab is opened.

This PR also adds partial support for the `SGR 6` _rapid blink_
attribute. This is not used by DEC terminals, but was defined in the
ECMA/ANSI standards. It's not widely supported, but many terminals just
it implement it as an alias for the regular `SGR 5` blink attribute, so
that's what I've done here too.

## Validation Steps Performed

I've checked the _Graphic rendition test pattern_ in Vttest, and
compared our representation of the blink attribute to that of an actual
DEC VT220 terminal as seen on [YouTube]. With the right color scheme
it's a reasonably close match.

[YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s

Closes #7388
2020-09-21 23:21:33 +00:00
Dustin L. Howett
206131d83a Update Cascadia Code to 2009.21 (#7693) 2020-09-21 12:40:34 -07:00
Dustin L. Howett
1e3236c87d Update userDefaults from "keybindings" to "actions" (#7692)
* Update userDefaults from "keybindings" to "actions"

* dfgdsafretgjhfg
2020-09-21 12:39:27 -07:00
Dustin L. Howett
f6cc0202b1 Wrap the textblock containing the "invalid" URI (#7694)
It looks much better this way.
2020-09-21 12:39:10 -07:00
Dustin L. Howett
ef83aa3c41 Make sure we don't hide the cursor until the IME starts (#7673)
Some IME implementations do not produce composition strings, and their
users have come to rely on the cursor that conhost traditionally left on
until a composition string showed up. We shouldn't hide the cursor until
we get a string (as opposed to hiding it when composition begins) so as
to not break those IMEs.

Related to #6207.

Fixes MSFT:29219348
2020-09-18 19:25:39 +00:00
Leon Liang
468c8c6728 Give Tab ownership of its SwitchToTab command (#7659)
Currently, `CommandPalette` creates and maintains the `SwitchToTab`
commands used for the ATS. When `Command` goes into the
TerminalSettingsModel, the palette won't be able to access `Command`'s
implementation type, making it difficult for `CommandPalette` to tell
`Command` to listen to `Tab` for changes.

This PR changes the relationship up so `Tab` now manages its
`SwitchToTab` command, and `CommandPalette` just plops the command from
`Tab` into its list.
2020-09-17 17:13:11 -07:00
Dustin L. Howett
d1981b531f Update Cascadia Code to 2009.14 (#7648)
2009.14 brings support for the Salishan language family and some bug fixes.
2020-09-17 19:41:22 +00:00
Carlos Zamora
b70ffdf790 Update ColorScheme with Json Serializer and color table API (#7609)
Add `ToJson()` to the `ConversionTrait`s in JsonUtils. This can be used
to serialize settings objects into JSON.

As a proof of concept, `ToJson` and `UpdateJson` were added to
`ColorScheme`.

Getters and setters for members and colors in the color table were added
and polished.

## References
#1564 - Settings UI

`ColorScheme` is a particularly easy example of serialization because it
has _no fallback_.

Added a few tests for JSON serializers.
2020-09-17 11:27:46 -07:00
James Holderness
c0335940a0 Fix failing HyperlinkIdConsistency unit test (#7655)
## Summary of the Pull Request

This fixes a typo in the `HyperlinkIdConsistency` unit test which was causing that test to fail. It was mistakenly using a `/` instead of `\` for the string terminator sequences.

## References

The test initially worked because of a bug in the state machine parser, but that bug was recently fixed in PR #7340.

## PR Checklist
* [x] Closes #7654
* [x] CLA signed. 
* [x] Tests 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.

## Validation Steps Performed

I've run the test again and it now passes.
2020-09-17 16:52:42 +00:00
Dustin L. Howett
5d823f538c Replace the "user docs" with references to the real docs (#7649) 2020-09-17 09:38:01 -07:00
Chester Liu
f91b53d5fd Preprocess and convert C1 controls to their 7 bit equivalent (#7340)
C1 control characters are now first converted to their 7 bit equivalent.
This allows us to unify the logic of C1 and C0 escape handling. This
also adds support for SOS/PM/APC string.

* Unify the logic for C1 and C0 escape handling by converting C1 to C0
  beforehand. This adds support for various C1 characters, including
  IND(8/4), NEL(8/5), HTS(8/8), RI(8/13), SS2(8/14), SS3(8/15),
  OSC(9/13), etc. 
* Add support for SOS/PM/APC escape sequences. Fixes #7032
* Use "Variable Length String" logic to unify the string termination
  handling of OSC, DCS and SOS/PM/APC. This fixes an issue where OSC
  action is successfully dispatched even when terminated with non-ST
  character. Introduced by #6328, the DCS PassThrough is spared from
  this issue. This PR puts them together and add test cases for them.

References:
https://vt100.net/docs/vt510-rm/chapter4.html
https://vt100.net/emu/dec_ansi_parser

Closes #7032
Closes #7317
2020-09-16 22:30:46 +00:00
Dustin Howett
863e3e5c22 Fix code format from inbox
It appears as though the pragma above broke the format below.
2020-09-16 10:19:49 -07:00
Dustin Howett
515c9f2c42 Merge remote-tracking branch 'openconsole/inbox' into main 2020-09-15 18:50:40 -07:00
Dustin Howett
6c7a3aca3f Merged PR 5181334: OS build fixes on top of abf8805e0
til::color
It turns out that clang/gcc are okay with the anonymous
struct, but the CL we use in Windows was not.
2020-09-16 01:50:07 +00:00
Dustin Howett
2f8b3c45d3 Merged PR 5181181: Migrate OSS up to abf8805e0
Related work items: MSFT-29391837
2020-09-16 00:56:52 +00:00
Carlos Zamora
abf8805e00 Introduce KeyMapping and Move TerminalSettings construction (#7537)
`KeyMapping` was introduced to break up `AppKeyBindings`. `KeyMapping`
records the keybindings from the JSON and lets you query them.
`AppKeyBindings` now just holds a `ShortcutActionDispatcher` to run
actions, and a `KeyMapping` to record/query your existing keybindings.
This refactor allows `KeyMapping` to be moved to the
TerminalSettingsModel, and `ShortcutActionDispatcher` and
`AppKeyBindings` will stay in TerminalApp.

`AppKeyBindings` had to be passed down to a terminal via
`TerminalSettings`. Since each settings object had its own
responsibility to update/create a `TerminalSettings` object, I moved all
of that logic to `TerminalSettings`. This helps with the
TerminalSettingsModel refactor, and makes the construction of
`TerminalSettings` a bit cleaner and more centralized.

## References
#885 - this is all in preparation for the TerminalSettingsModel

## Validation Steps Performed
- [x] Tests passed
- [X] Deployment succeeded
2020-09-14 20:38:56 +00:00
Dustin L. Howett
c17f448d73 Make til::color's COLORREF conversion more optimal (#7619)
Clang (10) has no trouble optimizing the COLORREF conversion operator to
a simple 32-bit load with mask (!) even though it's a series of bit
shifts across multiple struct members.

MSVC (19.24) doesn't make the same optimization decision, and it emits
three 8-bit loads and some shifting.

In any case, the optimization only applies at -O2 (clang) and above.

In this commit, we leverage the spec-legality of using unions for type
conversions and the overlap of four uint8_ts and a uint32_t to make the
conversion very obvious to both compilers.

x86_64 msvc | O0 | O1 | O2
------------|----|----|--------------------
shifts      | 12 | 11 | 11 (fully inlined)
union       |  5 |  1 |  1 (fully inlined)

x86_64 clang | O0 | O1 | O2 + O3
-------------|----|----|--------------------
shifts       | 14 |  5 |  1 (fully inlined)
union        |  9 |  3 |  1 (fully inlined)

j4james brought up some concerns about til::color's minor wastefulness
in https://github.com/microsoft/terminal/pull/7578#discussion_r487355989.

This is a clear, simple transformation that saves us a few instructions
in a relatively common case, so I'm accepting a micro-optimization even
though we don't have data showing this to be a hot spot.
2020-09-14 18:51:03 +00:00
PankajBhojwani
88d1527985 Fix OSC8 termination over the PTY after SGR 0 (#7608)
We were prematurely clearing the hyperlink ID by resetting the
_lastTextAttributes. We should only clear the fields we want
cleared.

Fixes #7597.
2020-09-11 11:00:31 -07:00
Carlos Zamora
892cf05fe6 Add serialization error handling to settings projection layer (#7576)
Now that CascadiaSettings is a WinRT object, we need to update the error
handling a bit. Making it a WinRT object limits our errors to be
hresults. So we moved all the error handling down a layer to when we
load the settings object.

- Warnings encountered during validation are saved to `Warnings()`.
- Errors encountered during validation are saved to `GetLoadingError()`.
- Deserialization errors (mainly from JsonUtils) are saved to
  `GetDeserializationErrorMessage()`.

## References
#7141 - CascadiaSettings is a settings object
#885 - this makes ripping out CascadiaSettings into
     TerminalSettingsModel much easier

## Validation Steps Performed
* [x] Tests passed
- [x] Deployment succeeded
   - tested with invalid JSON (deserialization error)
   - tested with missing DefaultProfile (validation error)
2020-09-10 17:57:02 -07:00
PankajBhojwani
1377dbcbf4 Open up content dialogs for invalid URIs and unsupported schemes (#7523)
If a user clicks a link that is either invalid (cannot be parsed) or has
a scheme we do not support (like file or mailto (for now)), we open up a
dialog box telling them the issue.

References #5001
2020-09-10 17:55:36 -07:00
Dustin L. Howett
c3ddfab0bd oss: add a manifest for x11's rgb database (#7600)
We will want to add support for the x11 color names.
The name table is are MIT-licensed by the X.org Foundation.
2020-09-10 17:35:36 -07:00
PankajBhojwani
be50e563e6 Display URI tooltip, render dashed/solid underline for links (#7420)
- Render hyperlinks with a dashed underline
- Render hovered hyperlinks with a solid underline
- Show URI tooltip on hover

TermControl now has a canvas that contains a tiny border to which a
tooltip is attached. When we hover over hyperlinked text, we move the
border to the mouse location and update the tooltip content with the
URI. 

Introduced a new underline type (HyperlinkUnderline), supports rendering
for it, and uses it to render hyperlinks. HyperlinkUnderline is usually
a dashed underline, but when a link is hovered, all text with the same
hyperlink ID is rendered with a solid underline. 

References #5001
2020-09-10 14:59:56 -07:00
Dustin L. Howett
cb037f3953 Switch all DSR responses to appending instead of prepending (#7583)
This fixes an issue where two CPRs could end up corrupted in the input
buffer. An application that sent two CPRs back-to-back could
end up reading the first few characters of the first prepended CPR
before handing us another CPR. We would dutifully prepend it to the
buffer, causing them to overlap.

```
^[^[2;2R[1;1R
^^      ^^^^^ First CPR
  ^^^^^^ Second CPR
```

The end result of this corruption is that a requesting application
would receive an unbidden `R` on stdin; for vim, this would trigger
replace mode immediately on startup.

Response prepending was implemented in !997738 without much comment.
There's very little in the way of audit trail as to why we switched.
Michael believes that we wanted to make sure that applications got DSR
responses immediately. It had the unfortunate side effect of causing
subsequence CPRs across cursor moves to come out in the wrong order.

I discussed our options with him, and he suggested that we could
implement a priority queue in InputBuffer and make sure that "response"
input was dispatched to a client application before any application- or
user-generated input. This was deemed to be too much work.

We decided that DSR responses getting top billing was likely to be a
stronger guarantee than most terminals are capable of giving, and that
we should be fine if we just switch it back to append.

Thanks to @k-takata, @tekki and @brammool for the investigation on the
vim side.

Fixes #1637.
2020-09-09 23:55:22 +00:00
Dustin L. Howett
27f7ce7c6e Destruct ConptyConnection on a background thread (#7575)
This commit leverages C++/WinRT's final_release [extension point] to
pull the final destruction of ConptyConnection off onto a background
thread.

We've been seeing some deadlocks during teardown where the output thread
(holding the last owning reference to the connection) was trying to
destruct the threadpool wait while the threadpool wait was
simultaneously running its callback and waiting for the output thread to
terminate. It turns out that trying to release a threadpool wait while
it's running a callback that's blocked on you will absolutely result in
a deadlock.

Fixes #7392.

[extension point]: https://devblogs.microsoft.com/oldnewthing/20191018-00/?p=103010
2020-09-09 23:17:33 +00:00
Kayla Cinnamon
1c7ee65c5f Add initial settings UI spec (#6720)
## Summary of the Pull Request

This is the spec for the overall functionality of the settings UI - #1564.

There are proposals for the launch method and editing and saving settings that we should discuss.


## Detailed Description of the Pull Request / Additional comments

### [spec.md](https://github.com/microsoft/terminal/blob/cinnamon/spec-settings-ui/doc/specs/%231564%20-%20Settings%20UI/spec.md)
### [design.md](https://github.com/microsoft/terminal/blob/cinnamon/spec-settings-ui/doc/specs/%231564%20-%20Settings%20UI/design.md)
2020-09-09 16:11:27 -07:00
Carlos Zamora
c5cf7b817a Make CascadiaSettings a WinRT object (#7457)
CascadiaSettings is now a WinRT object in the TerminalApp project.

## References
#7141 - CascadiaSettings is a settings object
#885 - this new settings object will be moved to a new TerminalSettingsModel project

This one _looks_ big, but most of it is really just propagating the
changes to the tests. In fact, you can probably save yourself some time
because the tests were about an hour of Find&Replace.

`CascadiaSettings::GetCurrentAppSettings()` was only being used in
Pane.cpp. So I ripped out the 3 lines of code and stuffed them in there.

Follow-up work:
- There's a few places in AppLogic where I `get_self` to be able to get
  the warnings out. This will go away in the next PR (wrapping up #885)

## Validation Steps Performed
- [x] Tests passed
- [X] Deployment succeeded

Closes #7141
2020-09-09 20:49:53 +00:00
Casper Verhaar
c28efc3c4f Remove AcrylicOpacity from AzureCloudShellGenerator (#7573)
Removed Acrylic Opacity from AzureCloudShellGenerator.

## PR Checklist
* [x] Closes #7245 
* [x] CLA signed
* [x] I've discussed this with core contributors already
2020-09-09 00:13:53 +00:00
Dustin L. Howett
b2cfd0adad jumplist: prefer the app execution alias in %LOCALAPPDATA% (#7567)
By setting the jumplist entries to launch `WindowsTerminal.exe` out of
the package root, we've inadvertently made WindowsTerminalDev emit jump
list entries that launch the _unpackaged_ version of Terminal.

We can fix this by copying the code from the shell extension that
determines which version of the executable to launch -- wt, wtd or
WindowsTerminal -- depending on the context under which it was invoked.

Fixes #7554
2020-09-08 19:19:12 +00:00
Dustin Howett
230b86c990 Revert "Update to a newer MUX prerelease; remove workaround for compact sizing (#7447)"
This reverts commit 5330759c0f.

Fixes #7553
2020-09-08 09:25:30 -07:00
Bill Dengler
7a03f75ee9 Keep degenerate UIA text ranges degenerate after movement (#7530)
Conhost expands UIA text ranges when moved. This means that degenerate
ranges become non-degenerate after movement, leading to odd behaviour
from UIA clients. This PR doesn't expand degenerate ranges, but rather
keeps them degenerate by moving `_end` to the newly-changed `_start`.

Tested in the NVDA Python console (cases with `setEndPoint` and
`compareEndPoints` described in #7342). Also ran the logic by
@michaeldcurran.

Closes #7342

Almost definitely addresses nvaccess/nvda#11288 (although I'll need to
test with my Braille display). Also fixes an issue privately reported to
me by @simon818 with copy/paste from review cursor which originally lead
me to believe the issue was with `moveEndPointByRange`.
2020-09-04 20:59:38 +00:00
Chester Liu
7ab4d45a9d Add support for DECSCUSR "0" to restore cursor to user default (#7379)
This PR is about the behavior of DECSCUSR. This PR changes the meaning
of DECSCUSR 0 to restore the cursor style back to user default. This
differs from what VT spec says but it’s used in popular terminal
emulators like iTerm2 and VTE-based ones. See #1604. 

Another change is that for parameter greater than 6, DECSCUSR should be
ignored, instead of restoring the cursor to legacy. This PR fixes it.
See #7382.

Fixes #1604.
2020-09-04 20:36:09 +00:00
Marcel Wagner
5ba992a803 docs: use unlikely example versions in nuget package script (#7448)
* Update doc

* Change last digit
2020-09-04 13:01:51 -07:00
Marcel Wagner
5330759c0f Update to a newer MUX prerelease; remove workaround for compact sizing (#7447)
Update the WinUI version which allows us to remove the workaround.

Closes #6681
2020-09-04 20:00:40 +00:00
Leon Liang
9279b7a73d Add profiles to the Jumplist (#7515)
This commit introduces Jumplist customization and an item for each
profile to the Jumplist. Selecting an entry in the jumplist will pretty
much just execute  `wt.exe -p "{profile guid}"`, and so a new Terminal
will open with the selected profile.

Closes #576
2020-09-03 23:35:41 +00:00
Dustin Howett
97c2ccf08b Merge remote-tracking branch 'openconsole/inbox' into HEAD 2020-09-03 16:06:12 -07:00
Dustin Howett
4c75ffb327 Merged PR 5131018: [Git2Git] Migrate OS changes to console property sheet manifest
Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_onecore_dep_uxp dd0c54d9abd94dea1ffe956373a4c20b30a6151e

Related work items: MSFT-26187783
2020-09-03 21:38:04 +00:00
Bill Dengler
c808ed94a5 Prevent crash when attempting to select an out-of-bounds UIA text range (#7504)
When attempting to select a text range from a different text buffer (such as a standard text range when in alt mode), conhost crashes. This PR checks for this case and returns `E_FAIL` instead, preventing this crash.

## PR Checklist
* [x] Closes unfiled crash issue
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Passes manual test below
* [ ] 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
Ran the following lines in the NVDA Python console (NVDA+control+z) before and after this PR, and observed that Conhost no longer crashes after the change:

``` Python console
>>> # SSH to a remote Linux system
>>> ti=nav.makeTextInfo("caret")
>>> ti.move("line", -2)
-2
>>> # Switch away from the NVDA Python console, and run Nano in conhost. Then:
>>> ti.updateSelection() # Calls select() on the underlying UIA text range
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "NVDAObjects\UIA\__init__.pyc", line 790, in updateSelection
  File "comtypesMonkeyPatches.pyc", line 26, in __call__
_ctypes.COMError: (-2147220991, 'An event was unable to invoke any of the subscribers', (None, None, None, 0, None))
```
2020-09-03 18:06:43 +00:00
PankajBhojwani
614507b95b OSC 8 support for conhost and terminal (#7251)
<!-- 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
Conhost can now support OSC8 sequences (as specified [here](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)). Terminal also supports those sequences and additionally hyperlinks can be opened by Ctrl+LeftClicking on them. 

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

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [X] Closes #204 
* [ ] 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
Added support to:

- parse OSC8 sequences and extract URIs from them (conhost and terminal)
- add hyperlink uri data to textbuffer/screeninformation, associated with a hyperlink id (conhost and terminal)
- attach hyperlink ids to text to allow for uri extraction from the textbuffer/screeninformation (conhost and terminal)
- process ctrl+leftclick to open a hyperlink in the clicked region if present

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Open up a PowerShell tab and type
```PowerShell
${ESC}=[char]27
Write-Host "${ESC}]8;;https://github.com/microsoft/terminal${ESC}\This is a link!${ESC}]8;;${ESC}\"
```
Ctrl+LeftClick on the link correctly brings you to the terminal page on github

![hyperlink](https://user-images.githubusercontent.com/26824113/89953536-45a6f580-dbfd-11ea-8e0d-8a3cd25c634a.gif)
2020-09-03 13:52:39 -04:00
Carlos Zamora
7803efa6fe Make GlobalAppSettings a WinRT object (#7349)
GlobalAppSettings is now a WinRT object in the TerminalApp project.

## References
#7141 - GlobalAppSettings is a settings object
#885 - this new settings object will be moved to a new TerminalSettingsModel project

## PR Checklist
* [x] Tests passed

## Detailed Description of the Pull Request / Additional comments
This one was probably the easiest thus far.

The only weird thing is how we handle InitialPosition. Today, we lose a
little bit of fidelity when we convert from LaunchPosition (int) -->
Point (float) --> RECT (long). The current change converts
LaunchPosition (optional<long>) --> InitialPosition (long) --> RECT
(long).

NOTE: Though I could use LaunchPosition to go directly from TermApp to
AppHost, I decided to introduce InitialPosition because LaunchPosition
will be a part of TerminalSettingsModel soon.

## Validation Steps Performed
- [x] Tests passed
- [x] Deployment succeeded
2020-08-28 03:49:16 +00:00
Carlos Zamora
a51091c615 Make Profile a WinRT object (#7283)
Profile is now a WinRT object in the TerminalApp project.

As with ColorScheme, all of the serialization logic is not exposed via
the idl. TerminalSetingsModel will handle it when it's all moved over.

I removed the "Get" and "Set" prefixes from all of the Profile
functions. It just makes more sense to use the `GETSET_PROPERTY` macro
to do most of the work for us.

`CloseOnExitMode` is now an enum off of the Profile.idl.

`std::optional<wstring>` got converted to `hstring` (as opposed to
`IReference<hstring>`). `IReference<hstring>` is not valid to MIDL.

## References
#7141 - Profile is a settings object
#885 - this new settings object will be moved to a new TerminalSettingsModel project

## Validation Steps Performed
- [x] Tests passed
- [x] Deployment succeeded

Closes #7435
2020-08-28 01:09:22 +00:00
Dustin Howett
6c0e6d94cd ci: run CI and triggered builds on feature/* 2020-08-27 17:29:14 -07:00
Kayla Cinnamon
9283781579 Fix schema for setColorScheme (#7433)
`setColorScheme` should require `colorScheme` rather than `name`
2020-08-27 10:11:05 -07:00
Dustin Howett
f357e379fc Merged PR 5097423: Migrate OSS up to 0488c532
Dustin L. Howett
* Clear the last error before calling Mb2Wc in ConvertToW (GH-7391)
* Update clang-format to 10.0 (GH-7389)
* Add til::static_map, a constexpr key-value store (GH-7323)

James Holderness
* Refactor VT control sequence identification (CC-7304)

Mike Griese
* Compensate for VS 16.7, part 2 (GH-7383)
* Add support for iterable, nested commands (GH-6856)

Michael Niksa
* Helix Testing (GH-6992)
* Compensate for new warnings and STL changes in VS 16.7 (GH-7319)

nathpete-msft
* Fix environment block creation (GH-7401)

Chester Liu
* Add initial support for VT DCS sequences (CC-6328)

Related work items: #28791050
2020-08-26 20:11:55 +00:00
Dustin L. Howett
0488c5322c Update Cascadia Code to 2008.25 (#7403) 2020-08-25 14:50:51 -07:00
Carlos Zamora
2fdc88f7ea Make index in closeOtherTabs and closeTabsAfter optional (#7390)
## Summary of the Pull Request
The `index` action argument is now optional for `closeOtherTabs` and `closeTabsAfter`. When `index` is not defined, `index` is set to the focused tab's index.

Also, adds the non-index version of these actions to defaults.json.

## PR Checklist
* [X] Closes #7181 
* [X] CLA signed
* [X] Tests 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.

## Validation Steps Performed
Opened 4 tabs and ran closeOtherTabs/closeTabsAfter from command palette.
2020-08-25 19:25:25 +00:00
nathpete-msft
64f10a0c9d Fix environment block creation (#7401)
This fixes a regression in environment variable loading introduced as part
of the new environment block creation that prevents some system-defined,
volatile environment variables from being defined.

## References
https://github.com/microsoft/terminal/pull/7243#discussion_r476603599

## Validation Steps Performed
Manually verified locally.

Closes #7399
2020-08-25 18:16:48 +00:00
Leonard Hecker
ac310d98b7 Fixed #7372: Setting "altGrAliasing" to "false" disables AltGr (#7400)
## Summary of the Pull Request

Previously, if `altGrAliasing` was disabled, all `Ctrl+Alt` combinations were considered to be aliases of `AltGr` including `AltGr` itself and thus considered as key and not character events. But `AltGr` should not be treated as an alias of itself of course, as that prevents one from entering `AltGr` combinations entirely.

## PR Checklist
* [x] Closes #7372
* [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
* [ ] 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

* Activate a German keyboard layout
* Run `showkey -a` in WSL
* **Ensure** that `AltGr+Q` produces `@`
* **Ensure** that `Ctrl+Alt+Q` produces `@`
* Disable `altGrAliasing`
* **Ensure** that `AltGr+Q` produces `@`
* **Ensure** that `Ctrl+Alt+Q` produces `^[^Q`
2020-08-25 18:04:23 +00:00
Dustin L. Howett
4aecbf3833 Clear the last error before calling Mb2Wc in ConvertToW (#7391)
When the console functional tests are running on OneCoreUAP, the
newly-introduced (65bd4e327, #4309) FillOutputCharacterA tests will
actually fail because of radio interference on the return value of GLE.

Fixes MSFT-28163465
2020-08-25 17:17:21 +00:00
Dustin L. Howett
dbbe820ae4 Update clang-format to 10.0 (#7389)
This commit removes our local copy of clang-format 8 and replaces it
with a newly-built nuget package containing clang-format 10.

This resolves the inconsistency between our version of clang-format and
the one shipped in Visual Studio.

A couple minor format changes were either required or erroneously forced
upon us--chief among them is a redistribution of `*`s around SAL
annotations in inline class members of COM classes. Don't ask why; I
couldn't figure it out.

We had some aspirational goals for our formatting, which were left in
but commented out. Enabling them changes our format a little more than
I'm comfortable with, so I uncommented them and locked them to the
format style we've been using for the past year. We may not love it, but
our aspirations may not matter here any longer. Consistent formatting is
better than perfect formatting.
2020-08-25 17:15:43 +00:00
Kayla Cinnamon
6acb9f8c90 schema: swap closeTabsAfter and closeOtherTabs (#7386)
The descriptions were flipped, so I unflipped them.
2020-08-24 16:28:11 -07:00
Dustin Howett
c15b808142 version: bump to 1.4 on master
Signed-off-by: Dustin Howett <duhowett@microsoft.com>
2020-08-24 16:16:10 -07:00
Dustin L. Howett
a5bed25417 TermControl: set the scrollbar jump distance to one screenful (#7385)
Most applications with scrollable content seem to define the "large
jump" distance as about a screenful of content. You can see this in long
pages in Settings and documents in Notepad.

We just weren't configuring ScrollBar here.

Fixes #7367
2020-08-24 15:54:02 -07:00
Mike Griese
17e0c11840 Bind the command palette by default (#7384)
Bind the command palette to Ctrl+Shift+P by default, to enable it for all users in v1.3
2020-08-24 15:44:36 -07:00
Mike Griese
f897ce0a9f Add support for Commandline Mode to the CmdPal (#7293)
## Summary of the Pull Request

Adds support for "commandline mode" to the command palette. 
![cmdpal-commandline-mode](https://user-images.githubusercontent.com/18356694/90263053-bbd17500-de14-11ea-8726-fee48fec5888.gif)


This allows the user to start typing a `wt.exe` commandline directly in the command palette, to run that commandline directly in the current window. This allows the user input something like `> nt -p Ubuntu ; sp -p ssh` and open up a new tab and split it _in the current window_. 

## References

* cmdpal megathread: #5400
* Kinda related to #4472
* built with the `wt` action from #6537

## PR Checklist
* [x] Closes #6677
* [x] I work here
* [ ] Tests added/passed
* [ ] Requires documentation to be updated - sure does, when the cmdpal docs are written in the first place :P

## Validation Steps Performed

Tested manually
2020-08-24 21:39:07 +00:00
Mike Griese
55b6ace79f Compensate for VS 16.7, part 2 (#7383)
This is just the `noexcept` part of #7319, because the CI apparently got updated overnight.
2020-08-24 21:21:53 +00:00
Mike Griese
58efe791d1 Add icons to commands in the Command Palette (#7368)
## Summary of the Pull Request

![cmdpal-icons](https://user-images.githubusercontent.com/18356694/90916410-97dada00-e3a6-11ea-9fb0-755938a68a05.gif)

Adds support for setting a command's `icon`. This supports a couple different scenarios:
* setting a path to an image
* on `"iterateOn": "profiles"` commands, setting the icon to `${profile.icon}` (to use the profile's icon)
* setting the icon to a symbol from [Segoe MDL2 Assets](https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font)
* setting the icon to an emoji
* setting the icon to a character (what is an emoji other than a character, after all?)

## References
* Big s/o to @leonMSFT in #6732, who really did all the hard work here.

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

## Detailed Description of the Pull Request / Additional comments

Importantly, the creation of these icons must occur on the UI thread. That's why it's done in a "load the path from json", then "get the actual IconSource" structure.

## Validation Steps Performed
see the gif
2020-08-21 18:08:02 +00:00
Leon Liang
3d370dc652 Provide global setting to use ATS for nextTab and prevTab (#7321)
<!-- 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 splits the anchored and unanchored tab switcher into two. The anchored tab switcher is now baked into `nextTab`/`prevTab`, and the unanchored tab switcher command is just named `tabSearch`. `tabSearch` takes no arguments. To reflect this distinction, `CommandPalette.cpp` now refers to one as `TabSwitchMode` and the other as `TabSearchMode`.

I've added a global setting named `useTabSwitcher` (name up for debate) that makes the Terminal use the anchored tab switcher experience for `nextTab` and `prevTab`. 

I've also given the control the ability to detect <kbd>Alt</kbd> KeyUp events and to dispatch keybinding events. By listening for keybindings, the ATS can react to `nextTab`/`prevTab` invocations for navigation in addition to listening for <kbd>tab</kbd> and the arrow keys.

Closes #7178 
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] CLA signed.
* [x] Documentation updates: microsoftdocs/terminal#107
* [x] Schema updated.
2020-08-21 15:39:40 +00:00
Mike Griese
64e3c848c0 Add a pair of nested, iterable default commands (#7348)
## Summary of the Pull Request
![cmdpal-default-nested-commands](https://user-images.githubusercontent.com/18356694/90684483-e6b13400-e22d-11ea-8ca6-fe90ca8d9e82.gif)

Adds a pair of top-level commands that both have nested, iterable sub-commands. The "New Tab..." command has one child for each profile, and will open a new tab for that profile. The "Split Pane..." command similarly has a nested command for each profile, and also has a nested command for split auto/horizontal/vertical.

## References

* megathread: #5400 
* Would look better with icons from  #6644

## PR Checklist
* [x] Closes #7174 
* [x] I work here
* [ ] Tests added/passed
* [n/a] Requires documentation to be updated
2020-08-21 14:54:43 +00:00
Mike Griese
e238dcb84d Fix intellisense errors by moving TerminalApp projects around (#6897)
The easiest fix was actually just moving all the source files from
`TerminalApp` to `TerminalApp/lib`, where the appropriate `pch.h`
actually resides.

Closes #6866
2020-08-20 22:44:37 +00:00
Michelle Tan
6f991d312e Make sure we check to profile shortcuts with an index >9 (#7344)
Closes #7325
2020-08-20 14:58:13 -07:00
Mike Griese
2c4b868b20 Replace "bindings" with "actions" (#7332)
In #6532, we thought it would be a good idea to add "bindings" as an
overload for "keybindings", as we were no longer going to use the
keybindings array for just keybindings. We were going to add commands.
So we started secretly treating `"bindings"` the same as
`"keybindings"`.

Then, in #7175, we discussed using "actions" as the key for the list of
commands/keybindings/global actions, instead of using "bindings". We're
going to be using this array as the global list of all actions, so it
makes sense to just call it `"actions"`. 

This PR renames "bindings" to "actions". Fortunately, we never
documented the "bindings" overload in the first place, so we can get
away with this safely, and preferably before we ship "bindings" for too
long.

References #6899
2020-08-20 01:02:42 +00:00
Mike Griese
4814c4f81d Add togglePaneZoom to schema, defaults, and sort action names (#7346)
#6989 forgot to add `togglePaneZoom` to the schema, so this does that. 

WHILE I'M HERE:
* The action names in the schema and the actual source were both in _random_ order, so I sorted them alphabetically.
* I also added an unbound `togglePaneZoom` command to defaults.json, so users can use that command from the cmdpal w/o binding it manually.
2020-08-20 01:02:10 +00:00
Michael Niksa
5a0deca3d8 Set ProcessTestResults job to use conditions specified in parent (#7347)
Activating a template doesn't actually process conditions. Only jobs, stages, and tasks can process a condition. So specify the full condition in the parent template call as a parameter and ask the child job (who can actually evaluate the condition) to use that parameter to determine if it should run.
2020-08-19 19:31:03 +00:00
Mike Griese
eecdd53eb8 Add support for commands iterable on color schemes (#7329)
## Summary of the Pull Request

![cmdpal-set-color-scheme](https://user-images.githubusercontent.com/18356694/90517094-8eddd480-e12a-11ea-8be4-8b6782d8d88c.gif)

Allows for creating commands that iterate over the user's color schemes. Also adds a top-level nested command to `defaults.json` that allows the user to select a color scheme (pictured above). I'm not sure there are really any other use cases that make sense, but it _really_ makes sense for this one.

## References
* #5400 - cmdpal megathread
* made possible by #6856, _and support from viewers like you._
* All this is being done in pursuit of #6689 

## PR Checklist
* [x] Closes wait what? I could have swore there was an issue for this one...
* [x] I work here
* [x] Tests added/passed
* [ ] Requires documentation to be updated - okay maybe now I'll write some docs

## Detailed Description of the Pull Request / Additional comments

Most of the hard work for this was already done in #6856. This is just another thing to iterate over.

## Validation Steps Performed
* Played with this default command. It works great.
* Added tests.
2020-08-19 17:33:19 +00:00
Javier
20b7fe4ef4 Expose selection background and alpha through the WPF control (#7339)
Adds the ability to set the selection background opacity when setting the
selection background. This also exposes the selection background and alpha
through the terminal WPF container.
2020-08-18 16:11:41 -07:00
Mike Griese
b8b0dd75cf Fix broken localtests (#7333) 2020-08-18 14:50:02 -07:00
Mike Griese
3d64921120 Add some polish to nested commands in the command palette (#7299)
## Summary of the Pull Request

![cmdpal-nested-command-polish](https://user-images.githubusercontent.com/18356694/90293616-1f29ca00-de4a-11ea-8942-00d255de929a.gif)


* Add a chevron for nested commands
* Add the text of the parent command when entering a child command

## References

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

## Validation Steps Performed
_look at that gif_
2020-08-18 19:32:17 +00:00
James Holderness
7fcff4d33a Refactor VT control sequence identification (#7304)
This PR changes the way VT control sequences are identified and
dispatched, to be more efficient and easier to extend. Instead of
parsing the intermediate characters into a vector, and then having to
identify a sequence using both that vector and the final char, we now
use just a single `uint64_t` value as the identifier.

The way the identifier is constructed is by taking the private parameter
prefix, each of the intermediate characters, and then the final
character, and shifting them into a 64-bit integer one byte at a time,
in reverse order. For example, the `DECTLTC` control has a private
parameter prefix of `?`, one intermediate of `'`, and a final character
of `s`. The ASCII values of those characters are `0x3F`, `0x27`, and
`0x73` respectively, and reversing them gets you 0x73273F, so that would
then be the identifier for the control.

The reason for storing them in reverse order, is because sometimes we
need to look at the first intermediate to determine the operation, and
treat the rest of the sequence as a kind of sub-identifier (the
character set designation sequences are one example of this). When in
reverse order, this can easily be achieved by masking off the low byte
to get the first intermediate, and then shifting the value right by 8
bits to get a new identifier with the rest of the sequence.

With 64 bits we have enough space for a private prefix, six
intermediates, and the final char, which is way more than we should ever
need (the _DEC STD 070_ specification recommends supporting at least
three intermediates, but in practice we're unlikely to see more than
two).

With this new way of identifying controls, it should now be possible for
every action code to be unique (for the most part). So I've also used
this PR to clean up the action codes a bit, splitting the codes for the
escape sequences from the control sequences, and sorting them into
alphabetical order (which also does a reasonable job of clustering
associated controls).

## Validation Steps Performed

I think the existing unit tests should be good enough to confirm that
all sequences are still being dispatched correctly. However, I've also
manually tested a number of sequences to make sure they were still
working as expected, in particular those that used intermediates, since
they were the most affected by the dispatch code refactoring.

Since these changes also affected the input state machine, I've done
some manual testing of the conpty keyboard handling (both with and
without the new Win32 input mode enabled) to make sure the keyboard VT
sequences were processed correctly. I've also manually tested the
various VT mouse modes in Vttest to confirm that they were still working
correctly too.

Closes #7276
2020-08-18 18:57:52 +00:00
Michael Niksa
5d082ffe67 Helix Testing (#6992)
Use the Helix testing orchestration framework to run our Terminal LocalTests and Console Host UIA tests.

## References
#### Creates the following new issues:
- #7281 - re-enable local tests that were disabled to turn on Helix
- #7282 - re-enable UIA tests that were disabled to turn on Helix
- #7286 - investigate and implement appropriate compromise solution to how Skipped is handled by MUX Helix scripts

#### Consumes from:
- #7164 - The update to TAEF includes wttlog.dll. The WTT logs are what MUX's Helix scripts use to track the run state, convert to XUnit format, and notify both Helix and AzDO of what's going on.

#### Produces for:
- #671 - Making Terminal UIA tests is now possible
- #6963 - MUX's Helix scripts are already ready to capture PGO data on the Helix machines as certain tests run. Presuming we can author some reasonable scenarios, turning on the Helix environment gets us a good way toward automated PGO.

#### Related:
- #4490 - We lost the AzDO integration of our test data when I moved from the TAEF/VSTest adapter directly back to TE. Thanks to the WTTLog + Helix conversion scripts to XUnit + new upload phase, we have it back!

## PR Checklist
* [x] Closes #3838
* [x] I work here.
* [x] Literally adds tests.
* [ ] Should I update a testing doc in this repo?
* [x] Am core contributor. Hear me roar.
* [ ] Correct spell-checking the right way before merge.

## Detailed Description of the Pull Request / Additional comments
We have had two classes of tests that don't work in our usual build-machine testing environment:
1. Tests that require interactive UI automation or input injection (a.k.a. require a logged in user)
2. Tests that require the entire Windows Terminal to stand up (because our Xaml Islands dependency requires 1903 or later and the Windows Server instance for the build is based on 1809.)

The Helix testing environment solves both of these and is brought to us by our friends over in https://github.com/microsoft/microsoft-ui-xaml.

This PR takes a large portion of scripts and pipeline configuration steps from the Microsoft-UI-XAML repository and adjusts them for Terminal needs.
You can see the source of most of the files in either https://github.com/microsoft/microsoft-ui-xaml/tree/master/build/Helix or https://github.com/microsoft/microsoft-ui-xaml/tree/master/build/AzurePipelinesTemplates

Some of the modifications in the files include (but are not limited to) reasons like:
- Our test binaries are named differently than MUX's test binaries
- We don't need certain types of testing that MUX does.
- We use C++ and C# tests while MUX was using only C# tests (so the naming pattern and some of the parsing of those names is different e.g. :: separators in C++ and . separators in C#)
- Our pipeline phases work a bit differently than MUX and/or we need significantly fewer pieces to the testing matrix (like we don't test a wide variety of OS versions).

The build now runs in a few stages:
1. The usual build and run of unit tests/feature tests, packaging verification, and whatnot. This phase now also picks up and packs anything required for running tests in Helix into an artifact. (It also unifies the artifact name between the things Helix needs and the existing build outputs into the single `drop` artifact to make life a little easier.)
2. The Helix preparation build runs that picks up those artifacts, generates all the scripts required for Helix to understand the test modules/functions from our existing TAEF tests, packs it all up, and queues it on the Helix pool.
3. Helix generates a VM for our testing environment and runs all the TAEF tests that require it. The orchestrator at helix.dot.net watches over this and tracks the success/fail and progress of each module and function. The scripts from our MUX friends handle installing dependencies, making the system quiet for better reliability, detecting flaky tests and rerunning them, and coordinating all the log uploads (including for the subruns of tests that are re-run.)
4. A final build phase is run to look through the results with the Helix API and clean up the marking of tests that are flaky, link all the screenshots and console output logs into the AzDO tests panel, and other such niceities.

We are set to run Helix tests on the Feature test policy of only x64 for now. 

Additionally, because the set up of the Helix VMs takes so long, we are *NOT* running these in PR trigger right now as I believe we all very much value our 15ish minute PR turnaround (and the VM takes another 15 minutes to just get going for whatever reason.) For now, they will only run as a rolling build on master after PRs are merged. We should still know when there's an issue within about an hour of something merging and multiple PRs merging fast will be done on the rolling build as a batch run (not one per).

In addition to setting up the entire Helix testing pipeline for the tests that require it, I've preserved our classic way of running unit and feature tests (that don't require an elaborate environment) directly on the build machines. But with one bonus feature... They now use some of the scripts from MUX to transform their log data and report it to AzDO so it shows up beautifully in the build report. (We used to have this before I removed the MStest/VStest wrapper for performance reasons, but now we can have reporting AND performance!) See https://dev.azure.com/ms/terminal/_build/results?buildId=101654&view=ms.vss-test-web.build-test-results-tab for an example. 

I explored running all of the tests on Helix but.... the Helix setup time is long and the resources are more expensive. I felt it was better to preserve the "quick signal" by continuing to run these directly on the build machine (and skipping the more expensive/slow Helix setup if they fail.) It also works well with the split between PR builds not running Helix and the rolling build running Helix. PR builds will get a good chunk of tests for a quick turn around and the rolling build will finish the more thorough job a bit more slowly.

## Validation Steps Performed
- [x] Ran the updated pipelines with Pull Request configuration ensuring that Helix tests don't run in the usual CI
- [x] Ran with simulation of the rolling build to ensure that the tests now running in Helix will pass. All failures marked for follow on in reference issues.
2020-08-18 18:23:24 +00:00
Michael Niksa
97c52c6503 fix spell check, how did I miss that. 2020-08-18 11:14:38 -07:00
Rashil Gandhi
6eea6a3ced Add Scoop (unofficial) installation method (#7303) 2020-08-18 11:08:22 -07:00
Dustin L. Howett
66fd9c367d Add til::static_map, a constexpr key-value store (#7323)
This is based on (cribbed almost directly from) code written by the
inimitable @StephanTLavavej on one of our mailing lists.

This is a nice generic version of the approach used in
JsonUtils::EnumMapper and CodepointWidthDetector: a static array of
key-value pairs that we binary-search at runtime (or at compile time, as
the case may be.)

Keys are not required to be sorted, as we're taking advantage of
constexpr std::sort (VS 16.6+) to get the compiler to do it for us. How
cool is that?

static_map presents an operator[] or at much like
std::map/std::unordered_map does.

I've added some tests, but they're practically fully-solveable at compile
time so they pretty much act like `VERIFY_IS_TRUE(true)`.
2020-08-18 18:05:14 +00:00
Michelle Tan
93d266925c Add menu (also known as "app") as a bindable key (#7328)
- Add MENU key with "menu" "app" as key bindings.
- Updated profiles.schema.json and documentation.

## Validation Steps Performed
Ran tests locally.
Tested out the new key binding.
```{ "command": "openNewTabDropdown", "keys": "app" }```

Closes #7144
2020-08-18 17:21:50 +00:00
Michael Niksa
a50c48cd60 Compensate for new warnings and STL changes in VS 16.7 (#7319)
New warnings were added in VS 16.7 and `std::map::erase` is now `noexcept`.
Update our code to be compatible with the new enforcement.

## PR Checklist
* [x] Closes broken audit in main after Agents updated over the weekend.
* [x] I work here.
* [x] Audit mode passes now
* [x] Am core contributor.

## Validation Steps Performed
* [x] Ran audit mode locally
2020-08-18 16:59:31 +00:00
Jorge Cabot
8943f68d4d Add startOnUserLogin & fullscreen launchMode to schema (#7300)
Fixes #7294

Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-08-17 17:24:00 -07:00
Michelle Tan
baefa46238 Update colour picker buttons with round edges. (#7305)
![RoundedButtons](https://user-images.githubusercontent.com/41475767/90323225-39e96500-df56-11ea-9219-d386f74fc1b4.png)

## Validation Steps Performed
Deployed locally and verified that the colour picker's button has round edges.

Closes #7142
2020-08-18 00:21:11 +00:00
Dustin L. Howett
c4a9752be1 Disable parallel build (again) and keep TerminalApp PCHs (#7322)
The build now builds every project multiple times, so I figure, why not
try to fix it.
2020-08-18 00:01:50 +00:00
Chester Liu
acac35023d Add initial support for VT DCS sequences (#6328)
As the title suggests, this commit adds initial support for the VT DCS
sequences. The parameters are parsed but not yet used. The pass through
data is yet to be handled. This effectively fixes #120 by making Sixel
graphics sequences *ignored* instead of printed.

* https://vt100.net/docs/vt510-rm/chapter4.html
* https://vt100.net/emu/dec_ansi_parser

Tests added.

References #448
Closes #120
2020-08-17 10:30:07 -07:00
Carlos Zamora
24b8c13bd0 Add copyFormatting keybinding arg and array support (#6004)
Adds array support for the existing `copyFormatting` global setting.
This allows users to define which formats they would specifically like
to be copied.

A boolean value is still accepted and is translated to the following:
- `false` --> `"none"` or `[]`
- `true` --> `"all"` or `["html", "rtf"]`

This also adds `copyFormatting` as a keybinding arg for `copy`. As with
the global setting, a boolean value and array value is accepted.

CopyFormat is a WinRT enum where each accepted format is a flag.
Currently accepted formats include `html`, and `rtf`. A boolean value is
accepted and converted. `true` is a conjunction of all the formats.
`false` only includes plain text.

For the global setting, `null` is not accepted. We already have a
default value from before so no worries there.

For the keybinding arg, `null` (the default value) means that we just do
what the global arg says to do. Overall, the `copyFormatting` keybinding
arg is an override of the global setting **when using that keybinding**.

References #5212 - Spec for formatted copying
References #2690 - disable html copy

Validated behavior with every combination of values below:
- `copyFormatting` global: { `true`, `false`, `[]`, `["html"]` }
- `copyFormatting` copy arg:
  { `null`, `true`, `false`, `[]`, `[, "html"]`}

Closes #4191
Closes #5262
2020-08-14 18:02:24 -07:00
Carlos Zamora
e9a7053629 Make ColorScheme a WinRT object (#7238)
ColorScheme is now a WinRT object.

All of the JSON stuff can't be exposed via the idl. So the plan here is
that we'll have the TerminalSettingsModel project handle all of the
serialization when it's moved over. These functions will be exposed off
of the `implementation` namespace, not projected namespace.

References #7141 - ColorScheme is a settings object
References #885 - this new settings object will be moved to a new
TerminalSettingsModel project

## Validation Steps Performed
- [x] Tests passed
- [x] Deployment succeeded
2020-08-14 17:54:35 -07:00
Dustin L. Howett
aecd99e0ca Pass the scancode in our tunneled DirectKey event (#7298)
#7145 introduced a check so that we wouldn't dispatch keys unless they
actually had a scancode. Our synthetic events actually _didn't_ have
scancodes. Not because they couldn't--just because they didn't.

Fixes #7297
2020-08-14 23:44:39 +00:00
Mike Griese
dcc2799457 Add support for iterable, nested commands (#6856)
## Summary of the Pull Request

This PR adds support for both _nested_ and _iterable_ commands in the Command palette.
![nested-commands-000](https://user-images.githubusercontent.com/18356694/87072916-2d991c00-c1e2-11ea-8917-a70e8b8b9803.gif)

* **Nested commands**: These are commands that include additional sub-commands. When the user selects on of these, the palette will update to only show the nested commands.
* **Iterable commands**: These are commands what allow the user to define only a single command, which is repeated once for every profile. (in the future, also repeated for color schemes, themes, etc.)

The above gif uses the following json:

```json
        {
            "name": "Split Pane...",
            "commands": [
                {
                    "iterateOn": "profiles",
                    "name": "Split with ${profile.name}...",
                    "commands": [
                        { "command": { "action": "splitPane", "profile": "${profile.name}", "split": "automatic" } },
                        { "command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" } },
                        { "command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" } }
                    ]
                }
            ]
        },
```

## References

## PR Checklist
* [x] Closes #3994
* [x] I work here
* [x] Tests added/passed
* [ ] Requires documentation to be updated - Sure does, but we'll finish polishing this first.

## Detailed Description of the Pull Request / Additional comments

We've now gotta keep the original json for a command around, so that once we know what all the profiles will be, we can expand the commands that need it. 

We've also got to parse commands recursively, because they might have any number of child commands.

These together made the command parsing a _lot_ more complicated, but it feels good so far.

## Validation Steps Performed
* wrote a bunch of tests
* Played with it a bunch
2020-08-13 21:22:46 +00:00
Leon Liang
d9ffca6614 Remove unnecessary check when updating ATS indices (#7280)
Removes the if-statement in `UpdateTabIndices` that blocks all scenarios where you delete the second to last tab. This fixes the issue where the ATS gets confused about which item in the ListView is associated with which tab.

Closes #7278
2020-08-13 20:35:29 +00:00
Mike Griese
01e3fda91b Don't zoom when there's only one pane (#7273)
This is a minor fix from #6989. If there's only one pane in the
Terminal, then we'd still "zoom" it and give it a border, but all the
borders would be black. 

A single pane is already "zoomed", so it doesn't really make sense to
try and zoom if there's only one.
2020-08-13 19:17:58 +00:00
Leon Liang
93ae6b6dba Default initialize a CmdPal mode (#7263)
Whoops, members are zero initialized in Debug builds but most likely not
in Release builds So, this PR adds a couple of default values to
`_currentMode` and its associated XAML strings to make cmdpal/ats work
deterministically on first use.  I also added a default value to
`_anchorKey` just to be safe.

Closes #7254
2020-08-12 23:25:50 +00:00
Kayla Cinnamon
a02a29783e doc: Add Pankaj to our README (#7266)
We have a new team member!
2020-08-12 15:05:04 -07:00
Dustin Howett
e1cdc2776f Merged PR 5039910: Migrate OSS up to a2721c104
Carlos Zamora (1)
* Pass mouse button state into HandleMouse instead of asking win32 (GH-6765)

James Holderness (1)
* Add support for the "doubly underlined" graphic rendition attribute (CC-7223)

Moshe Schorr (1)
* Batch RTL runs to ensure proper draw order (CC-7190)

Related work items: MSFT-28385436
2020-08-12 18:03:59 +00:00
Leonard Hecker
a2721c1043 Fixed #3799: Introduce sendInput command (#7249)
## Summary of the Pull Request

This PR enables users to send arbitrary text input to the shell via a keybinding.

## PR Checklist
* [x] Closes #3799
* [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.
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #3799

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed

Added the following keybindings:
```json
{ "keys": "p", "command": { "action": "sendInput", "input": "foobar" } },
{ "keys": "q", "command": { "action": "sendInput", "input": "\u001b[A" } },
```
Ensured that when pressing <kbd>P</kbd> "foobar" is echoed to the shell and when pressing <kbd>Q</kbd> the shell history is being navigated backwards.
2020-08-12 13:46:53 +00:00
Mike Griese
a34cfa4e78 Mini-spec for New Tab Menu Customization (#5888)
* This is a mini-spec for how I see this working

* good bot

* These were some typos

* Addd a future consideration about the command palette and commands

* Update spec to reflect discussion with Carlos

* update spec to reflect investigations in Command Palette Addenda 1

* add references to #6899, and minor bits of review feedback

* add `remainingProfiles` as a way of adding all the user's other profiles quickly to the menu as well

* clarify why we're not doing it in the profiles list

* no two commits do not contain a misspelling of separate
2020-08-12 08:40:38 -05:00
Mike Griese
c241f83fb3 Spec for global action IDs (#7175)
## Summary of the Pull Request

⚠️ This spec has been moved from #6902. That version was branched off the new tab menu customization, and had a terribly convoluted git history. After discussion with the team, we've decided that it's best that this spec is merged atomically _first_, and used as the basis for #5888, as opposed to the other way around.

> This document is intended to serve as an addition to the [Command Palette Spec],
> as well as the [New Tab Menu Customization Spec].
> 
> As we come to rely more on actions being a mechanism by which the user defines
> "do something in the Terminal", we'll want to make it even easier for users to
> re-use the actions that they've already defined, as to reduce duplicated json as
> much as possible. This spec proposes a mechanism by which actions could be
> uniquely identifiable, so that the user could refer to bindings in other
> contexts without needing to replicate an entire json blob.
> 

## PR Checklist
* [x] Specs: #6899
* [x] References: #1571, #1912, #3337, #5025, #5524, #5633
* [x] I work here

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



[Command Palette Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Command%20Palette.md
[New Tab Menu Customization Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md
2020-08-12 08:39:21 -05:00
Dustin L. Howett
849243af99 Always create a new environment block before we spawn a process (#7243)
This commit ensures that we always furnish a new process with the
cleanest, most up-to-date environment variables we can. There is a minor
cost here in that WT will no longer pass environment variables that it
itself inherited to its child processes.

This could be considered a reasonable sacrifice. It will also remove
somebody else's TERM, TERM_PROGRAM and TERM_PROGRAM_VERSION from the
environment, which could be considered a win.

I validated  that GetCurrentProcessToken returns a token we're
_technically able_ to use with this API; it is roughly equivalent to
OpenProcessToken(GetCurrentProcess) in that it returns the current
active _access token_ (which is what CreateEnvironmentBlock wants.)

There's been discussion about doing a 3-way merge between WT's
environment and the new one. This will be complicated and I'd like to
scream test the 0-way merge first ;P

Related to #1125 (but it does not close it or resolve any of the other
issues it calls out.)

Fixes #7239
Fixes #7204 ("App Paths" value creeping into wt's environment)
2020-08-11 23:58:45 +00:00
Mike Griese
fe82e97ff6 Enable partial rebuilds of the TerminalControl project again (#7248)
This regressed around the #7163 timeframe.

We're discussing this on chat currently. It might break the intellisense
on the `#include <winrt/Microsoft.Terminal.TerminalControl.h>` line in
VS 16.7, but we're not _really_ sure? Intellisense has been notoriously
flaky for us.

I'm running 16.6.5, and it works for me. @lhecker is running 16.7 and
confirmed it worked there. If the CI build passes, then this definitely
will work for 16.7.
2020-08-11 16:56:58 -07:00
Dustin L. Howett
c5d5500758 tools: add Get-OSSConhostLog (#7250)
This script takes a range of commits and generates a commit log with the
git2git-excluded file changes filtered out.

It also replaces GitHub issue numbers with GH-XXX so as to not confuse
Git2Git or Azure DevOps.  Community contributions are tagged with CC- so
they can be detected later.

The output looks like this:

```
Carlos Zamora (2)
* Pass mouse button state into HandleMouse instead of asking win32 (GH-6765)

Dustin L. Howett (6)
* Disable MinimalCoreWin when OpenConsoleUniversalApp is false (GH-7203)

James Holderness (1)
* Add support for the "doubly underlined" graphic rendition attribute (CC-7223)
```

Yes, the numbers are wrong. No, it doesn't really matter.
2020-08-11 20:08:03 +00:00
Mike Griese
bc642bbf2a Fix viewport moving when we've scrolled up and circled the buffer (#7247)
If you scroll up to view the scrollback, then we want the viewport to
"stay in place", as new output comes in (see #6062). This works fine up
until the buffer circles. In this case, the mutable viewport isn't
actually moving, so we never set `updatedViewport` to true. 

This regressed in #6062
Closes #7222
2020-08-11 19:57:45 +00:00
Dustin L. Howett
7ccd1f6f1a Display meaningful errors when JSON types don't match (#7241)
This pull request completes (and somewhat rewrites) the JsonUtils error
handling arc. Deserialization errors, no longer represented by trees of
exceptions that must be rethrown and caught, are now transformed at
catch time into a message explaining what we expected and where we
expected it.

Instead of exception trees, a deserialization failure will result in a
single type of exception with the originating JSON object from which we
can determine the contents and location of the failure.

Because most of the error message actually comes from the JSON schema
or the actual supported types, and the other jsoncpp errors are not
localized I've made the decision to **not** localize these messages.
2020-08-11 19:50:13 +00:00
Leon Liang
b07c1e49da Advanced Tab Switcher (#6732)
![TabSwitchingv2](https://user-images.githubusercontent.com/57155886/88237962-5505d500-cc35-11ea-8384-d91699155067.gif)

## Summary of the Pull Request
This PR adds the Advanced Tab Switcher (ATS) to Terminal. It'll work
similarly to VSCode's tab switcher. Because this implementation rides
off a lot of the Command Palette's XAML code, it'll look just like the
Command Palette, and also have support for tab title search.

## References
#3753 - ATS Spec

Closes #1502
2020-08-11 14:03:12 +00:00
Dustin L. Howett
c03677b0c9 Resolve the default profile during defaults load, don't crash on launch (#7237)
The "default profile as name" feature in 1.1 broke the loading of
default settings, as we would never get to the validation phase where
the default profile string was transformed into a guid.

I moved knowledge of the "unparsed default profile" optional to the
consumer so that we could make sure we only attempted to deserialize it
once (and only if it was present.)

Fixes #7236.

## PR Checklist
* [x] Closes #7236
2020-08-10 21:48:27 +00:00
Dustin L. Howett
e6c71cb62a Allow profile.padding to be an int (or any other type :|) (#7235)
## Summary of the Pull Request

We're expecting that people have treated `padding` as an integer, and the type-based converter is too strict for that. This PR widens its scope and explicitly allows for it in the schema.

## PR Checklist
* [x] Closes #7234
2020-08-10 19:48:33 +00:00
James Holderness
e7a1a675af Add support for the "doubly underlined" graphic rendition attribute (#7223)
This PR adds support for the ANSI _doubly underlined_ graphic rendition
attribute, which is enabled by the `SGR 21` escape sequence.

There was already an `ExtendedAttributes::DoublyUnderlined` flag in the
`TextAttribute` class, but I needed to add `SetDoublyUnderlined` and
`IsDoublyUnderlined` methods to access that flag, and update the
`SetGraphicsRendition` methods of the two dispatchers to set the
attribute on receipt of the `SGR 21` sequence. I also had to update the
existing `SGR 24` handler to reset _DoublyUnderlined_ in addition to
_Underlined_, since they share the same reset sequence.

For the rendering, I've added a new grid line type, which essentially
just draws an additional line with the same thickness as the regular
underline, but slightly below it - I found a gap of around 0.05 "em"
between the lines looked best. If there isn't enough space in the cell
for that gap, the second line will be clamped to overlap the first, so
you then just get a thicker line. If there isn't even enough space below
for a thicker line, we move the offset _above_ the first line, but just
enough to make it thicker.

The only other complication was the update of the `Xterm256Engine` in
the VT renderer. As mentioned above, the two underline attributes share
the same reset sequence, so to forward that state over conpty we require
a slightly more complicated process than with most other attributes
(similar to _Bold_ and _Faint_). We first check whether either underline
attribute needs to be turned off to send the reset sequence, and then
check individually if each of them needs to be turned back on again.

## Validation Steps Performed

For testing, I've extended the existing attribute tests in
`AdapterTest`, `VTRendererTest`, and `ScreenBufferTests`, to make sure
we're covering both the _Underlined_ and _DoublyUnderlined_ attributes.

I've also manually tested the `SGR 21` sequence in conhost and Windows
Terminal, with a variety of fonts and font sizes, to make sure the
rendering was reasonably distinguishable from a single underline.

Closes #2916
2020-08-10 17:06:16 +00:00
Mike Griese
aee803e694 Add support for changing the active color scheme with an action (#6993)
## Summary of the Pull Request

Adds the `setColorScheme` action, to change the color scheme of the active control to one given by the `name` parameter. `name` is required. If `name` is not the name of a color scheme, the action does nothing.

## References

* Being done as a stepping stone to #6689 

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

## Detailed Description of the Pull Request / Additional comments

Technically, the action is being done by changing the settings of the current `TerminalSettings` of the `TermControl`. Frankly, it should be operating on a copy of the `TermControl`'s `IControlSettings`, then updating the control's settings, or the Control should just listen for changes to it's setting's properties, and update in real time (without a manual call to `UpdateSettings`. However, both those paths are somewhere unknowable beyond #6904, so we'll just do this for now.

## Validation Steps Performed

* tested manually with a scheme that exists
* tested manually with a scheme that doesn't exist
2020-08-10 16:21:56 +00:00
Carlos Zamora
20a288020e Pass mouse button state into HandleMouse instead of asking win32 (#6765)
MouseInput was directly asking user32 about the state of the mouse buttons,
which was somewhat of a layering violation. This commit makes all callers
have to pass the mouse state in themselves.

Closes #4869
2020-08-07 16:21:09 -07:00
Mike Griese
70fd03f247 Add a togglePaneZoom action for zooming a pane (#6989)
This PR adds the `togglePaneZoom` action, which can be used to make a
pane expand to fill the entire contents of the window.  A tab that
contains a zoomed pane will have a magnifying glass icon prepended
to its title. Any attempts to manage panes with one zoomed will force
the zoomed pane back to normal size.

VALIDATION
Zoomed in and out a bunch. Tried closing panes while zoomed. Tried
splitting panes while zoomed. Etc.

Closes #996
2020-08-07 16:11:44 -07:00
Mike Griese
4e0f31337d Add support for per-profile tab colors (#7162)
This PR adds support for per-profile tab colors, in accordance with
#7134. This adds a single `tabColor` property, that when set, specifies
the background color for profile's tab. This color can be overridden by
the color picker, and clearing the color with the color picker will
revert to this default color set for the tab.

* Full theming is covered in #3327 & #5772 

Validation: Played with setting this color, both on launch and via
hot-reload

Specified in #7134
Closes #1337
2020-08-07 16:07:42 -07:00
Moshe Schorr
60b44c856e Batch RTL runs to ensure proper draw order (#7190)
Consecutive RTL GlyphRuns are drawn from the last to the first.

References
#538, #7149, all those issues asking for RTL closed as dupes.

As @miniksa suggested in a comment on #7149 -- handle the thingy on the
render side.

If we have GlyphRuns abcdEFGh, where EFG are RTL, we draw them now in
order abcdGFEh.

This has ransom-noting, because I didn't touch the font scaling at all.
This should fix the majority of RTL issues, except it *doesn't* fix
issues with colors, because those get split in the TextBuffer phase in
the renderer I think, so they show up separately by the GlyphRun phase.
2020-08-07 12:04:53 -07:00
411 changed files with 25773 additions and 12982 deletions

View File

@@ -1,17 +1,19 @@
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
#AllowAllArgumentsOnNextLine: false
AllowAllArgumentsOnNextLine: true
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
#AllowAllConstructorInitializersOnNextLine: false
AllowAllConstructorInitializersOnNextLine: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortFunctionsOnASingleLine: Inline
AllowShortCaseLabelsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: Never
#AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
@@ -20,6 +22,7 @@ AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
@@ -47,6 +50,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: false
FixNamespaceComments: false
IncludeBlocks: Regroup
@@ -73,7 +77,7 @@ ReflowComments: false
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
#SpaceAfterLogicalNot: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
@@ -88,6 +92,6 @@ SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
Standard: Latest
TabWidth: 4
UseTab: Never

40
.github/linters/.markdown-lint.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
---
###########################
###########################
## Markdown Linter rules ##
###########################
###########################
# Linter rules doc:
# - https://github.com/DavidAnson/markdownlint
#
# Note:
# To comment out a single error:
# <!-- markdownlint-disable -->
# any violations you want
# <!-- markdownlint-restore -->
#
# To run the linter locally:
# 1. install the npm package:
# `npm install -g markdownlint-cli`
# 2. Then run it in the root of the repo with
# `markdownlint -c .github\linters\.markdown-lint.yml ./*.md`
###############
# Rules by id #
###############
MD004: false # Unordered list style
MD007:
indent: 2 # Unordered list indentation
MD013:
line_length: 400 # Line length 80 is far to short
MD026:
punctuation: ".,;:!。,;:" # List of not allowed
MD029: false # Ordered list item prefix
MD033: false # Allow inline HTML
MD036: false # Emphasis used instead of a heading
#################
# Rules by tags #
#################
blank_lines: false # Error on blank lines

51
.github/workflows/linter.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
---
###########################
###########################
## Linter GitHub Actions ##
###########################
###########################
name: Lint Code Base
#
# Documentation:
# https://help.github.com/en/articles/workflow-syntax-for-github-actions
#
#############################
# Start the job on all push #
#############################
on:
pull_request:
branches: [master]
###############
# Set the Job #
###############
jobs:
build:
# Name the Job
name: Lint Code Base
# Set the agent to run on
runs-on: ubuntu-latest
##################
# Load all steps #
##################
steps:
##########################
# Checkout the code base #
##########################
- name: Checkout Code
uses: actions/checkout@v2
################################
# Run Linter against code base #
################################
- name: Lint Code Base
uses: github/super-linter@v3.10.0
env:
VALIDATE_ALL_CODEBASE: false
DEFAULT_BRANCH: master
MARKDOWN_CONFIG_FILE: .markdown-lint.yml
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_EDITORCONFIG: false

View File

@@ -140,6 +140,13 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and you
1. Create & push a feature branch
1. Create a [Draft Pull Request (PR)](https://github.blog/2019-02-14-introducing-draft-pull-requests/)
1. Work on your changes
1. Build and see if it works. Consult [How to build OpenConsole](./doc/building.md) if you have problems.
### Testing
Testing is a key component in the development workflow. Both Windows Terminal and Windows Console use TAEF(the Test Authoring and Execution Framework) as the main framework for testing.
If your changes affect existing test cases, or you're working on brand new features and also the accompanying test cases, see [TAEF](./doc/TAEF.md) for more information about how to validate your work locally.
### Code Review

View File

@@ -48,36 +48,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## telnetpp
**Source**: https://github.com/KazDragon/telnetpp
### License
```
The MIT License (MIT)
Copyright (c) 2015-2017 Matthew Chaplain a.k.a KazDragon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## chromium/base/numerics
**Source**: https://github.com/chromium/chromium/tree/master/base/numerics

View File

@@ -9,6 +9,11 @@
<!-- Use our own NuGet Feed -->
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
<!-- Temporarily? use the feeds from our friends in MUX for Helix test stuff -->
<add key="dotnetfeed" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
<add key="dnceng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="MUX-Dependencies" value="https://pkgs.dev.azure.com/ms/microsoft-ui-xaml/_packaging/MUX-Dependencies/nuget/v3/index.json" />
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />

View File

@@ -87,8 +87,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Feature", "src\h
ProjectSection(ProjectDependencies) = postProject
{18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263}
{FC802440-AD6A-4919-8F2C-7701F2B38D79} = {FC802440-AD6A-4919-8F2C-7701F2B38D79}
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalParser.UnitTests", "src\terminal\parser\ut_parser\Parser.UnitTests.vcxproj", "{12144E07-FE63-4D33-9231-748B8D8C3792}"
@@ -177,9 +177,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminal", "src\casc
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia\TerminalApp\TerminalApp.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia\TerminalApp\dll\TerminalApp.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746}
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
EndProjectSection
@@ -225,16 +226,21 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_TerminalApp", "src\cascadia\ut_app\TerminalApp.UnitTests.vcxproj", "{CA5CAD1A-9333-4D05-B12A-1905CBF112F9}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj", "{CA5CAD1A-9A12-429C-B551-8562EC954746}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\cascadia\TerminalApp\TerminalAppLib.vcxproj", "{CA5CAD1A-9A12-429C-B551-8562EC954746}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_TerminalApp", "src\cascadia\LocalTests_TerminalApp\TerminalApp.LocalTests.vcxproj", "{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746}
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererUia", "src\renderer\uia\lib\uia.vcxproj", "{48D21369-3D7B-4431-9967-24E81292CF63}"
@@ -250,6 +256,8 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestHostApp", "src\cascadia\LocalTests_TerminalApp\TestHostApp\TestHostApp.vcxproj", "{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
{CA5CAD1A-9B68-456A-B13E-C8218070DC42} = {CA5CAD1A-9B68-456A-B13E-C8218070DC42}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BDB237B6-1D1D-400F-84CC-40A58FA59C8E}"
@@ -307,6 +315,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTerminalTestNetCore", "s
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wt", "src\cascadia\wt\wt.vcxproj", "{506FD703-BAA7-4F6E-9361-64F550EC8FCA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings.Model.Lib", "src\cascadia\TerminalSettingsModel\Microsoft.Terminal.Settings.ModelLib.vcxproj", "{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings.Model", "src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj", "{CA5CAD1A-082C-4476-9F33-94B339494076}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_SettingsModel", "src\cascadia\LocalTests_SettingsModel\SettingsModel.LocalTests.vcxproj", "{CA5CAD1A-9B68-456A-B13E-C8218070DC42}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AuditMode|Any CPU = AuditMode|Any CPU
@@ -1986,6 +2012,84 @@ Global
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.Build.0 = Release|x64
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.ActiveCfg = Release|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.Build.0 = AuditMode|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|Any CPU.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.Build.0 = Debug|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.ActiveCfg = Debug|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.Build.0 = Debug|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.Build.0 = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|Any CPU.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.ActiveCfg = Release|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.Build.0 = Release|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.ActiveCfg = Release|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.Build.0 = Release|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.Build.0 = Release|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|x64.ActiveCfg = Release|x64
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|x86.Build.0 = AuditMode|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|Any CPU.ActiveCfg = Debug|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|ARM64.Build.0 = Debug|ARM64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|x64.ActiveCfg = Debug|x64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|x64.Build.0 = Debug|x64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|x86.ActiveCfg = Debug|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|x86.Build.0 = Debug|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|Any CPU.ActiveCfg = Release|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|ARM64.ActiveCfg = Release|ARM64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|ARM64.Build.0 = Release|ARM64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|x64.ActiveCfg = Release|x64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|x64.Build.0 = Release|x64
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|x86.ActiveCfg = Release|Win32
{CA5CAD1A-082C-4476-9F33-94B339494076}.Release|x86.Build.0 = Release|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|x64.ActiveCfg = AuditMode|x64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|x86.Build.0 = AuditMode|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|Any CPU.ActiveCfg = Debug|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|ARM64.Build.0 = Debug|ARM64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|x64.ActiveCfg = Debug|x64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|x64.Build.0 = Debug|x64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|x86.ActiveCfg = Debug|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|x86.Build.0 = Debug|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|Any CPU.ActiveCfg = Release|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|ARM64.ActiveCfg = Release|ARM64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|ARM64.Build.0 = Release|ARM64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|x64.ActiveCfg = Release|x64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|x64.Build.0 = Release|x64
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|x86.ActiveCfg = Release|Win32
{CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2066,6 +2170,9 @@ Global
{6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87}
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202}
{506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-082C-4476-9F33-94B339494076} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-9B68-456A-B13E-C8218070DC42} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}

234
README.md
View File

@@ -7,21 +7,25 @@ This repository contains the source code for:
* The Windows console host (`conhost.exe`)
* Components shared between the two projects
* [ColorTool](https://github.com/Microsoft/Terminal/tree/master/src/tools/ColorTool)
* [Sample projects](https://github.com/Microsoft/Terminal/tree/master/samples) that show how to consume the Windows Console APIs
* [Sample projects](https://github.com/Microsoft/Terminal/tree/master/samples)
that show how to consume the Windows Console APIs
Related repositories include:
* [Windows Terminal Documentation](https://docs.microsoft.com/windows/terminal) ([Repo: Contribute to the docs](https://github.com/MicrosoftDocs/terminal))
* [Windows Terminal Documentation](https://docs.microsoft.com/windows/terminal)
([Repo: Contribute to the docs](https://github.com/MicrosoftDocs/terminal))
* [Console API Documentation](https://github.com/MicrosoftDocs/Console-Docs)
* [Cascadia Code Font](https://github.com/Microsoft/Cascadia-Code)
## Installing and running Windows Terminal
> 👉 Note: Windows Terminal requires Windows 10 1903 (build 18362) or later
> 🔴 Note: Windows Terminal requires Windows 10 1903 (build 18362) or later
### Microsoft Store [Recommended]
Install the [Windows Terminal from the Microsoft Store][store-install-link]. This allows you to always be on the latest version when we release new builds with automatic upgrades.
Install the [Windows Terminal from the Microsoft Store][store-install-link].
This allows you to always be on the latest version when we release new builds
with automatic upgrades.
This is our preferred method.
@@ -29,16 +33,21 @@ This is our preferred method.
#### Via GitHub
For users who are unable to install Terminal from the Microsoft Store, Terminal builds can be manually downloaded from this repository's [Releases page](https://github.com/microsoft/terminal/releases).
For users who are unable to install Terminal from the Microsoft Store, Terminal
builds can be manually downloaded from this repository's [Releases
page](https://github.com/microsoft/terminal/releases).
> Note: If you install Terminal manually:
> 🔴 Note: If you install Terminal manually:
>
> * Be sure to install the [Desktop Bridge VC++ v14 Redistributable Package](https://www.microsoft.com/en-us/download/details.aspx?id=53175) otherwise Terminal may not install and/or run and may crash at startup
> * Terminal will not auto-update when new builds are released so you will need to regularly install the latest Terminal release to receive all the latest fixes and improvements!
> * Terminal will not auto-update when new builds are released so you will need
> to regularly install the latest Terminal release to receive all the latest
> fixes and improvements!
#### Via Windows Package Manager CLI (aka winget)
[winget](https://github.com/microsoft/winget-cli) users can download and install the latest Terminal release by installing the `Microsoft.WindowsTerminal` package:
[winget](https://github.com/microsoft/winget-cli) users can download and install
the latest Terminal release by installing the `Microsoft.WindowsTerminal`
package:
```powershell
winget install --id=Microsoft.WindowsTerminal -e
@@ -46,7 +55,8 @@ winget install --id=Microsoft.WindowsTerminal -e
#### Via Chocolatey (unofficial)
[Chocolatey](https://chocolatey.org) users can download and install the latest Terminal release by installing the `microsoft-windows-terminal` package:
[Chocolatey](https://chocolatey.org) users can download and install the latest
Terminal release by installing the `microsoft-windows-terminal` package:
```powershell
choco install microsoft-windows-terminal
@@ -58,70 +68,143 @@ To upgrade Windows Terminal using Chocolatey, run the following:
choco upgrade microsoft-windows-terminal
```
If you have any issues when installing/upgrading the package please go to the [Windows Terminal package page](https://chocolatey.org/packages/microsoft-windows-terminal) and follow the [Chocolatey triage process](https://chocolatey.org/docs/package-triage-process)
If you have any issues when installing/upgrading the package please go to the
[Windows Terminal package
page](https://chocolatey.org/packages/microsoft-windows-terminal) and follow the
[Chocolatey triage process](https://chocolatey.org/docs/package-triage-process)
#### Via Scoop (unofficial)
[Scoop](https://scoop.sh) users can download and install the latest Terminal
release by installing the `windows-terminal` package:
```powershell
scoop install windows-terminal
```
To update Windows Terminal using Scoop, run the following:
```powershell
scoop update windows-terminal
```
If you have any issues when installing/updating the package, please search for
or report the same on the [issues
page](https://github.com/lukesampson/scoop-extras/issues) of Scoop Extras bucket
repository.
---
## Windows Terminal 2.0 Roadmap
The plan for delivering Windows Terminal 2.0 [is described here](/doc/terminal-v2-roadmap.md) and will be updated as the project proceeds.
The plan for delivering Windows Terminal 2.0 [is described
here](/doc/terminal-v2-roadmap.md) and will be updated as the project proceeds.
## Project Build Status
Project|Build Status
---|---
Terminal|[![Build Status](https://dev.azure.com/ms/Terminal/_apis/build/status/Terminal%20CI?branchName=master)](https://dev.azure.com/ms/Terminal/_build?definitionId=136)
ColorTool|![](https://microsoft.visualstudio.com/_apis/public/build/definitions/c93e867a-8815-43c1-92c4-e7dd5404f1e1/17023/badge)
Terminal|[![Terminal Build Status](https://dev.azure.com/ms/Terminal/_apis/build/status/Terminal%20CI?branchName=master)](https://dev.azure.com/ms/Terminal/_build?definitionId=136)
ColorTool|![Colortool Build Status](https://microsoft.visualstudio.com/_apis/public/build/definitions/c93e867a-8815-43c1-92c4-e7dd5404f1e1/17023/badge)
---
## Terminal & Console Overview
Please take a few minutes to review the overview below before diving into the code:
Please take a few minutes to review the overview below before diving into the
code:
### Windows Terminal
Windows Terminal is a new, modern, feature-rich, productive terminal application for command-line users. It includes many of the features most frequently requested by the Windows command-line community including support for tabs, rich text, globalization, configurability, theming & styling, and more.
Windows Terminal is a new, modern, feature-rich, productive terminal application
for command-line users. It includes many of the features most frequently
requested by the Windows command-line community including support for tabs, rich
text, globalization, configurability, theming & styling, and more.
The Terminal will also need to meet our goals and measures to ensure it remains fast and efficient, and doesn't consume vast amounts of memory or power.
The Terminal will also need to meet our goals and measures to ensure it remains
fast and efficient, and doesn't consume vast amounts of memory or power.
### The Windows Console Host
The Windows Console host, `conhost.exe`, is Windows' original command-line user experience. It also hosts Windows' command-line infrastructure and the Windows Console API server, input engine, rendering engine, user preferences, etc. The console host code in this repository is the actual source from which the `conhost.exe` in Windows itself is built.
The Windows Console host, `conhost.exe`, is Windows' original command-line user
experience. It also hosts Windows' command-line infrastructure and the Windows
Console API server, input engine, rendering engine, user preferences, etc. The
console host code in this repository is the actual source from which the
`conhost.exe` in Windows itself is built.
Since taking ownership of the Windows command-line in 2014, the team added several new features to the Console, including background transparency, line-based selection, support for [ANSI / Virtual Terminal sequences](https://en.wikipedia.org/wiki/ANSI_escape_code), [24-bit color](https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/), a [Pseudoconsole ("ConPTY")](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/), and more.
Since taking ownership of the Windows command-line in 2014, the team added
several new features to the Console, including background transparency,
line-based selection, support for [ANSI / Virtual Terminal
sequences](https://en.wikipedia.org/wiki/ANSI_escape_code), [24-bit
color](https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/),
a [Pseudoconsole
("ConPTY")](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/),
and more.
However, because Windows Console's primary goal is to maintain backward compatibility, we have been unable to add many of the features the community (and the team) have been wanting for the last several years including tabs, unicode text, and emoji.
However, because Windows Console's primary goal is to maintain backward
compatibility, we have been unable to add many of the features the community
(and the team) have been wanting for the last several years including tabs,
unicode text, and emoji.
These limitations led us to create the new Windows Terminal.
> You can read more about the evolution of the command-line in general, and the Windows command-line specifically in [this accompanying series of blog posts](https://devblogs.microsoft.com/commandline/windows-command-line-backgrounder/) on the Command-Line team's blog.
> You can read more about the evolution of the command-line in general, and the
> Windows command-line specifically in [this accompanying series of blog
> posts](https://devblogs.microsoft.com/commandline/windows-command-line-backgrounder/)
> on the Command-Line team's blog.
### Shared Components
While overhauling Windows Console, we modernized its codebase considerably, cleanly separating logical entities into modules and classes, introduced some key extensibility points, replaced several old, home-grown collections and containers with safer, more efficient [STL containers](https://docs.microsoft.com/en-us/cpp/standard-library/stl-containers?view=vs-2019), and made the code simpler and safer by using Microsoft's [Windows Implementation Libraries - WIL](https://github.com/Microsoft/wil).
While overhauling Windows Console, we modernized its codebase considerably,
cleanly separating logical entities into modules and classes, introduced some
key extensibility points, replaced several old, home-grown collections and
containers with safer, more efficient [STL
containers](https://docs.microsoft.com/en-us/cpp/standard-library/stl-containers?view=vs-2019),
and made the code simpler and safer by using Microsoft's [Windows Implementation
Libraries - WIL](https://github.com/Microsoft/wil).
This overhaul resulted in several of Console's key components being available for re-use in any terminal implementation on Windows. These components include a new DirectWrite-based text layout and rendering engine, a text buffer capable of storing both UTF-16 and UTF-8, a VT parser/emitter, and more.
This overhaul resulted in several of Console's key components being available
for re-use in any terminal implementation on Windows. These components include a
new DirectWrite-based text layout and rendering engine, a text buffer capable of
storing both UTF-16 and UTF-8, a VT parser/emitter, and more.
### Creating the new Windows Terminal
When we started planning the new Windows Terminal application, we explored and evaluated several approaches and technology stacks. We ultimately decided that our goals would be best met by continuing our investment in our C++ codebase, which would allow us to reuse several of the aforementioned modernized components in both the existing Console and the new Terminal. Further, we realized that this would allow us to build much of the Terminal's core itself as a reusable UI control that others can incorporate into their own applications.
When we started planning the new Windows Terminal application, we explored and
evaluated several approaches and technology stacks. We ultimately decided that
our goals would be best met by continuing our investment in our C++ codebase,
which would allow us to reuse several of the aforementioned modernized
components in both the existing Console and the new Terminal. Further, we
realized that this would allow us to build much of the Terminal's core itself as
a reusable UI control that others can incorporate into their own applications.
The result of this work is contained within this repo and delivered as the Windows Terminal application you can download from the Microsoft Store, or [directly from this repo's releases](https://github.com/microsoft/terminal/releases).
The result of this work is contained within this repo and delivered as the
Windows Terminal application you can download from the Microsoft Store, or
[directly from this repo's
releases](https://github.com/microsoft/terminal/releases).
---
## Resources
For more information about Windows Terminal, you may find some of these resources useful and interesting:
For more information about Windows Terminal, you may find some of these
resources useful and interesting:
* [Command-Line Blog](https://devblogs.microsoft.com/commandline)
* [Command-Line Backgrounder Blog Series](https://devblogs.microsoft.com/commandline/windows-command-line-backgrounder/)
* Windows Terminal Launch: [Terminal "Sizzle Video"](https://www.youtube.com/watch?v=8gw0rXPMMPE&list=PLEHMQNlPj-Jzh9DkNpqipDGCZZuOwrQwR&index=2&t=0s)
* Windows Terminal Launch: [Build 2019 Session](https://www.youtube.com/watch?v=KMudkRcwjCw)
* Run As Radio: [Show 645 - Windows Terminal with Richard Turner](http://www.runasradio.com/Shows/Show/645)
* Azure Devops Podcast: [Episode 54 - Kayla Cinnamon and Rich Turner on DevOps on the Windows Terminal](http://azuredevopspodcast.clear-measure.com/kayla-cinnamon-and-rich-turner-on-devops-on-the-windows-terminal-team-episode-54)
* Microsoft Ignite 2019 Session: [The Modern Windows Command Line: Windows Terminal - BRK3321](https://myignite.techcommunity.microsoft.com/sessions/81329?source=sessions)
* [Command-Line Backgrounder Blog
Series](https://devblogs.microsoft.com/commandline/windows-command-line-backgrounder/)
* Windows Terminal Launch: [Terminal "Sizzle
Video"](https://www.youtube.com/watch?v=8gw0rXPMMPE&list=PLEHMQNlPj-Jzh9DkNpqipDGCZZuOwrQwR&index=2&t=0s)
* Windows Terminal Launch: [Build 2019
Session](https://www.youtube.com/watch?v=KMudkRcwjCw)
* Run As Radio: [Show 645 - Windows Terminal with Richard
Turner](http://www.runasradio.com/Shows/Show/645)
* Azure Devops Podcast: [Episode 54 - Kayla Cinnamon and Rich Turner on DevOps
on the Windows
Terminal](http://azuredevopspodcast.clear-measure.com/kayla-cinnamon-and-rich-turner-on-devops-on-the-windows-terminal-team-episode-54)
* Microsoft Ignite 2019 Session: [The Modern Windows Command Line: Windows
Terminal -
BRK3321](https://myignite.techcommunity.microsoft.com/sessions/81329?source=sessions)
---
@@ -131,48 +214,72 @@ For more information about Windows Terminal, you may find some of these resource
Cause: You're launching the incorrect solution in Visual Studio.
Solution: Make sure you're building & deploying the `CascadiaPackage` project in Visual Studio.
Solution: Make sure you're building & deploying the `CascadiaPackage` project in
Visual Studio.
> ⚠ Note: `OpenConsole.exe` is just a locally-built `conhost.exe`, the classic Windows Console that hosts Windows' command-line infrastructure. OpenConsole is used by Windows Terminal to connect to and communicate with command-line applications (via [ConPty](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/)).
> ⚠ Note: `OpenConsole.exe` is just a locally-built `conhost.exe`, the classic
> Windows Console that hosts Windows' command-line infrastructure. OpenConsole
> is used by Windows Terminal to connect to and communicate with command-line
> applications (via
> [ConPty](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/)).
---
## Documentation
All project documentation is located at aka.ms/terminal-docs. If you would like to contribute to the documentation, please submit a pull request on the [Windows Terminal Documentation repo](https://github.com/MicrosoftDocs/terminal).
All project documentation is located at aka.ms/terminal-docs. If you would like
to contribute to the documentation, please submit a pull request on the [Windows
Terminal Documentation repo](https://github.com/MicrosoftDocs/terminal).
---
## Contributing
We are excited to work alongside you, our amazing community, to build and enhance Windows Terminal\!
We are excited to work alongside you, our amazing community, to build and
enhance Windows Terminal\!
***BEFORE you start work on a feature/fix***, please read & follow our [Contributor's Guide](https://github.com/microsoft/terminal/blob/master/CONTRIBUTING.md) to help avoid any wasted or duplicate effort.
***BEFORE you start work on a feature/fix***, please read & follow our
[Contributor's
Guide](https://github.com/microsoft/terminal/blob/master/CONTRIBUTING.md) to
help avoid any wasted or duplicate effort.
## Communicating with the Team
The easiest way to communicate with the team is via GitHub issues.
Please file new issues, feature requests and suggestions, but **DO search for similar open/closed pre-existing issues before creating a new issue.**
Please file new issues, feature requests and suggestions, but **DO search for
similar open/closed pre-existing issues before creating a new issue.**
If you would like to ask a question that you feel doesn't warrant an issue (yet), please reach out to us via Twitter:
If you would like to ask a question that you feel doesn't warrant an issue
(yet), please reach out to us via Twitter:
* Kayla Cinnamon, Program Manager: [@cinnamon\_msft](https://twitter.com/cinnamon_msft)
* Kayla Cinnamon, Program Manager:
[@cinnamon\_msft](https://twitter.com/cinnamon_msft)
* Dustin Howett, Engineering Lead: [@dhowett](https://twitter.com/DHowett)
* Michael Niksa, Senior Developer: [@michaelniksa](https://twitter.com/MichaelNiksa)
* Michael Niksa, Senior Developer:
[@michaelniksa](https://twitter.com/MichaelNiksa)
* Mike Griese, Developer: [@zadjii](https://twitter.com/zadjii)
* Carlos Zamora, Developer: [@cazamor_msft](https://twitter.com/cazamor_msft)
* Leon Liang, Developer: [@leonmsft](https://twitter.com/leonmsft)
* Pankaj Bhojwani, Developer
## Developer Guidance
## Prerequisites
* You must be running Windows 1903 (build >= 10.0.18362.0) or later to run Windows Terminal
* You must [enable Developer Mode in the Windows Settings app](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) to locally install and run Windows Terminal
* You must have the [Windows 10 1903 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) installed
* You must have at least [VS 2019](https://visualstudio.microsoft.com/downloads/) installed
* You must install the following Workloads via the VS Installer. Note: Opening the solution in VS 2019 will [prompt you to install missing components automatically](https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/):
* You must be running Windows 1903 (build >= 10.0.18362.0) or later to run
Windows Terminal
* You must [enable Developer Mode in the Windows Settings
app](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development)
to locally install and run Windows Terminal
* You must have the [Windows 10 1903
SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk)
installed
* You must have at least [VS
2019](https://visualstudio.microsoft.com/downloads/) installed
* You must install the following Workloads via the VS Installer. Note: Opening
the solution in VS 2019 will [prompt you to install missing components
automatically](https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/):
* Desktop Development with C++
* Universal Windows Platform Development
* **The following Individual Components**
@@ -180,13 +287,17 @@ If you would like to ask a question that you feel doesn't warrant an issue (yet)
## Building the Code
This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) for some of its dependencies. To make sure submodules are restored or updated, be sure to run the following prior to building:
This repository uses [git
submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) for some of its
dependencies. To make sure submodules are restored or updated, be sure to run
the following prior to building:
```shell
git submodule update --init --recursive
```
OpenConsole.sln may be built from within Visual Studio or from the command-line using a set of convenience scripts & tools in the **/tools** directory:
OpenConsole.sln may be built from within Visual Studio or from the command-line
using a set of convenience scripts & tools in the **/tools** directory:
### Building in PowerShell
@@ -205,19 +316,28 @@ bcz
## Running & Debugging
To debug the Windows Terminal in VS, right click on `CascadiaPackage` (in the Solution Explorer) and go to properties. In the Debug menu, change "Application process" and "Background task process" to "Native Only".
To debug the Windows Terminal in VS, right click on `CascadiaPackage` (in the
Solution Explorer) and go to properties. In the Debug menu, change "Application
process" and "Background task process" to "Native Only".
You should then be able to build & debug the Terminal project by hitting <kbd>F5</kbd>.
You should then be able to build & debug the Terminal project by hitting
<kbd>F5</kbd>.
> 👉 You will _not_ be able to launch the Terminal directly by running the WindowsTerminal.exe. For more details on why, see [#926](https://github.com/microsoft/terminal/issues/926), [#4043](https://github.com/microsoft/terminal/issues/4043)
> 👉 You will _not_ be able to launch the Terminal directly by running the
> WindowsTerminal.exe. For more details on why, see
> [#926](https://github.com/microsoft/terminal/issues/926),
> [#4043](https://github.com/microsoft/terminal/issues/4043)
### Coding Guidance
Please review these brief docs below about our coding practices.
> 👉 If you find something missing from these docs, feel free to contribute to any of our documentation files anywhere in the repository (or write some new ones!)
> 👉 If you find something missing from these docs, feel free to contribute to
> any of our documentation files anywhere in the repository (or write some new
> ones!)
This is a work in progress as we learn what we'll need to provide people in order to be effective contributors to our project.
This is a work in progress as we learn what we'll need to provide people in
order to be effective contributors to our project.
* [Coding Style](https://github.com/Microsoft/Terminal/blob/master/doc/STYLE.md)
* [Code Organization](https://github.com/Microsoft/Terminal/blob/master/doc/ORGANIZATION.md)
@@ -226,10 +346,12 @@ This is a work in progress as we learn what we'll need to provide people in orde
---
# Code of Conduct
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code].
For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments.
This project has adopted the [Microsoft Open Source Code of
Conduct][conduct-code]. For more information see the [Code of Conduct
FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any
additional questions or comments.
[conduct-code]: https://opensource.microsoft.com/codeofconduct/
[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/

17
SUPPORT.md Normal file
View File

@@ -0,0 +1,17 @@
# Support
## How to file issues and get help
This project uses [GitHub issues][gh-issue] to [track bugs][gh-bug] and [feature requests][gh-feature]. Please search the existing issues before filing new issues to avoid duplicates. For new topics, file your bug or feature request as a new issue.
For help and questions about using this project, please look at the [docs site for Windows Terminal][docs] and our [Contributor's Guide][contributor] if you want to work on Windows Terminal.
## Microsoft Support Policy
Support for Windows Terminal is limited to the resources listed above.
[gh-issue]: https://github.com/microsoft/terminal/issues/new/choose
[gh-bug]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Bug&template=bug_report.md&title=
[gh-feature]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Feature&template=Feature_Request.md&title=
[docs]: https://docs.microsoft.com/windows/terminal
[contributor]: https://github.com/microsoft/terminal/blob/master/CONTRIBUTING.md

View File

@@ -0,0 +1,32 @@
function GetAzureDevOpsBaseUri
{
Param(
[string]$CollectionUri,
[string]$TeamProject
)
return $CollectionUri + $TeamProject
}
function GetQueryTestRunsUri
{
Param(
[string]$CollectionUri,
[string]$TeamProject,
[string]$BuildUri,
[switch]$IncludeRunDetails
)
if ($IncludeRunDetails)
{
$includeRunDetailsParameter = "&includeRunDetails=true"
}
else
{
$includeRunDetailsParameter = ""
}
$baseUri = GetAzureDevOpsBaseUri -CollectionUri $CollectionUri -TeamProject $TeamProject
$queryUri = "$baseUri/_apis/test/runs?buildUri=$BuildUri$includeRunDetailsParameter&api-version=5.0"
return $queryUri
}

View File

@@ -0,0 +1,28 @@
Param(
[Parameter(Mandatory = $true)]
[string]$WttInputPath,
[Parameter(Mandatory = $true)]
[string]$WttSingleRerunInputPath,
[Parameter(Mandatory = $true)]
[string]$WttMultipleRerunInputPath,
[Parameter(Mandatory = $true)]
[string]$XUnitOutputPath,
[Parameter(Mandatory = $true)]
[string]$TestNamePrefix
)
# Ideally these would be passed as parameters to the script. However ps makes it difficult to deal with string literals containing '&', so we just
# read the values directly from the environment variables
$helixResultsContainerUri = $Env:HELIX_RESULTS_CONTAINER_URI
$helixResultsContainerRsas = $Env:HELIX_RESULTS_CONTAINER_RSAS
$rerunPassesRequiredToAvoidFailure = $env:rerunPassesRequiredToAvoidFailure
Add-Type -Language CSharp -ReferencedAssemblies System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Runtime.Serialization.Json (Get-Content $PSScriptRoot\HelixTestHelpers.cs -Raw)
$testResultParser = [HelixTestHelpers.TestResultParser]::new($TestNamePrefix, $helixResultsContainerUri, $helixResultsContainerRsas)
$testResultParser.ConvertWttLogToXUnitLog($WttInputPath, $WttSingleRerunInputPath, $WttMultipleRerunInputPath, $XUnitOutputPath, $rerunPassesRequiredToAvoidFailure)

View File

@@ -0,0 +1,112 @@
$scriptDirectory = $script:MyInvocation.MyCommand.Path | Split-Path -Parent
# List all processes to aid debugging:
Write-Host "All processes running:"
Get-Process
tasklist /svc
# Add this test directory as an exclusion for Windows Defender
Write-Host "Add $scriptDirectory as Exclusion Path"
Add-MpPreference -ExclusionPath $scriptDirectory
Write-Host "Add $($env:HELIX_CORRELATION_PAYLOAD) as Exclusion Path"
Add-MpPreference -ExclusionPath $env:HELIX_CORRELATION_PAYLOAD
Get-MpPreference
Get-MpComputerStatus
# Minimize all windows:
$shell = New-Object -ComObject "Shell.Application"
$shell.minimizeall()
# Kill any instances of Windows Security Alert:
$windowTitleToMatch = "*Windows Security Alert*"
$procs = Get-Process | Where {$_.MainWindowTitle -like "*Windows Security Alert*"}
foreach ($proc in $procs)
{
Write-Host "Found process with '$windowTitleToMatch' title: $proc"
$proc.Kill();
}
# Kill processes by name that are known to interfere with our tests:
$processNamesToStop = @("Microsoft.Photos", "WinStore.App", "SkypeApp", "SkypeBackgroundHost", "OneDriveSetup", "OneDrive")
foreach($procName in $processNamesToStop)
{
Write-Host "Attempting to kill $procName if it is running"
Stop-Process -ProcessName $procName -Verbose -ErrorAction Ignore
}
Write-Host "All processes running after attempting to kill unwanted processes:"
Get-Process
tasklist /svc
$platform = $env:testbuildplatform
if(!$platform)
{
$platform = "x86"
}
function UninstallApps {
Param([string[]]$appsToUninstall)
foreach($pkgName in $appsToUninstall)
{
foreach($pkg in (Get-AppxPackage $pkgName).PackageFullName)
{
Write-Output "Removing: $pkg"
Remove-AppxPackage $pkg
}
}
}
function UninstallTestApps {
Param([string[]]$appsToUninstall)
foreach($pkgName in $appsToUninstall)
{
foreach($pkg in (Get-AppxPackage $pkgName).PackageFullName)
{
Write-Output "Removing: $pkg"
Remove-AppxPackage $pkg
}
# Sometimes an app can get into a state where it is no longer returned by Get-AppxPackage, but it is still present
# which prevents other versions of the app from being installed.
# To handle this, we can directly call Remove-AppxPackage against the full name of the package. However, without
# Get-AppxPackage to find the PackageFullName, we just have to manually construct the name.
$packageFullName = "$($pkgName)_1.0.0.0_$($platform)__8wekyb3d8bbwe"
Write-Host "Removing $packageFullName if installed"
Remove-AppPackage $packageFullName -ErrorVariable appxerror -ErrorAction SilentlyContinue
if($appxerror)
{
foreach($error in $appxerror)
{
# In most cases, Remove-AppPackage will fail due to the package not being found. Don't treat this as an error.
if(!($error.Exception.Message -match "0x80073CF1"))
{
Write-Error $error
}
}
}
else
{
Write-Host "Successfully removed $packageFullName"
}
}
}
Write-Host "Uninstall AppX packages that are known to cause issues with our tests"
UninstallApps("*Skype*", "*Windows.Photos*")
Write-Host "Uninstall any of our test apps that may have been left over from previous test runs"
UninstallTestApps("NugetPackageTestApp", "NugetPackageTestAppCX", "IXMPTestApp", "MUXControlsTestApp")
Write-Host "Uninstall MUX Framework package that may have been left over from previous test runs"
# We don't want to uninstall all versions of the MUX Framework package, as there may be other apps preinstalled on the system
# that depend on it. We only uninstall the Framework package that corresponds to the version of MUX that we are testing.
[xml]$versionData = (Get-Content "version.props")
$versionMajor = $versionData.GetElementsByTagName("MUXVersionMajor").'#text'
$versionMinor = $versionData.GetElementsByTagName("MUXVersionMinor").'#text'
UninstallApps("Microsoft.UI.Xaml.$versionMajor.$versionMinor")
Get-Process

View File

@@ -0,0 +1,336 @@
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $true)]
[string]$TestFile,
[Parameter(Mandatory = $true)]
[string]$OutputProjFile,
[Parameter(Mandatory = $true)]
[string]$JobTestSuiteName,
[Parameter(Mandatory = $true)]
[string]$TaefPath,
[string]$TaefQuery
)
Class TestCollection
{
[string]$Name
[string]$SetupMethodName
[string]$TeardownMethodName
[System.Collections.Generic.Dictionary[string, string]]$Properties
TestCollection()
{
if ($this.GetType() -eq [TestCollection])
{
throw "This class should never be instantiated directly; it should only be derived from."
}
}
TestCollection([string]$name)
{
$this.Init($name)
}
hidden Init([string]$name)
{
$this.Name = $name
$this.Properties = @{}
}
}
Class Test : TestCollection
{
Test([string]$name)
{
$this.Init($name)
}
}
Class TestClass : TestCollection
{
[System.Collections.Generic.List[Test]]$Tests
TestClass([string]$name)
{
$this.Init($name)
$this.Tests = @{}
}
}
Class TestModule : TestCollection
{
[System.Collections.Generic.List[TestClass]]$TestClasses
TestModule([string]$name)
{
$this.Init($name)
$this.TestClasses = @{}
}
}
function Parse-TestInfo([string]$taefOutput)
{
enum LineType
{
None
TestModule
TestClass
Test
Setup
Teardown
Property
}
[string]$testModuleIndentation = " "
[string]$testClassIndentation = " "
[string]$testIndentation = " "
[string]$setupBeginning = "Setup: "
[string]$teardownBeginning = "Teardown: "
[string]$propertyBeginning = "Property["
function Get-LineType([string]$line)
{
if ($line.Contains($setupBeginning))
{
return [LineType]::Setup;
}
elseif ($line.Contains($teardownBeginning))
{
return [LineType]::Teardown;
}
elseif ($line.Contains($propertyBeginning))
{
return [LineType]::Property;
}
elseif ($line.StartsWith($testModuleIndentation) -and -not $line.StartsWith("$testModuleIndentation "))
{
return [LineType]::TestModule;
}
elseif ($line.StartsWith($testClassIndentation) -and -not $line.StartsWith("$testClassIndentation "))
{
return [LineType]::TestClass;
}
elseif ($line.StartsWith($testIndentation) -and -not $line.StartsWith("$testIndentation "))
{
return [LineType]::Test;
}
else
{
return [LineType]::None;
}
}
[string[]]$lines = $taefOutput.Split(@([Environment]::NewLine, "`n"), [StringSplitOptions]::RemoveEmptyEntries)
[System.Collections.Generic.List[TestModule]]$testModules = @()
[TestModule]$currentTestModule = $null
[TestClass]$currentTestClass = $null
[Test]$currentTest = $null
[TestCollection]$lastTestCollection = $null
foreach ($rawLine in $lines)
{
[LineType]$lineType = (Get-LineType $rawLine)
# We don't need the whitespace around the line anymore, so we'll discard it to make things easier.
[string]$line = $rawLine.Trim()
if ($lineType -eq [LineType]::TestModule)
{
if ($currentTest -ne $null -and $currentTestClass -ne $null)
{
$currentTestClass.Tests.Add($currentTest)
}
if ($currentTestClass -ne $null -and $currentTestModule -ne $null)
{
$currentTestModule.TestClasses.Add($currentTestClass)
}
if ($currentTestModule -ne $null)
{
$testModules.Add($currentTestModule)
}
$currentTestModule = [TestModule]::new($line)
$currentTestClass = $null
$currentTest = $null
$lastTestCollection = $currentTestModule
}
elseif ($lineType -eq [LineType]::TestClass)
{
if ($currentTest -ne $null -and $currentTestClass -ne $null)
{
$currentTestClass.Tests.Add($currentTest)
}
if ($currentTestClass -ne $null -and $currentTestModule -ne $null)
{
$currentTestModule.TestClasses.Add($currentTestClass)
}
$currentTestClass = [TestClass]::new($line)
$currentTest = $null
$lastTestCollection = $currentTestClass
}
elseif ($lineType -eq [LineType]::Test)
{
if ($currentTest -ne $null -and $currentTestClass -ne $null)
{
$currentTestClass.Tests.Add($currentTest)
}
$currentTest = [Test]::new($line)
$lastTestCollection = $currentTest
}
elseif ($lineType -eq [LineType]::Setup)
{
if ($lastTestCollection -ne $null)
{
$lastTestCollection.SetupMethodName = $line.Replace($setupBeginning, "")
}
}
elseif ($lineType -eq [LineType]::Teardown)
{
if ($lastTestCollection -ne $null)
{
$lastTestCollection.TeardownMethodName = $line.Replace($teardownBeginning, "")
}
}
elseif ($lineType -eq [LineType]::Property)
{
if ($lastTestCollection -ne $null)
{
foreach ($match in [Regex]::Matches($line, "Property\[(.*)\]\s+=\s+(.*)"))
{
[string]$propertyKey = $match.Groups[1].Value;
[string]$propertyValue = $match.Groups[2].Value;
$lastTestCollection.Properties.Add($propertyKey, $propertyValue);
}
}
}
}
if ($currentTest -ne $null -and $currentTestClass -ne $null)
{
$currentTestClass.Tests.Add($currentTest)
}
if ($currentTestClass -ne $null -and $currentTestModule -ne $null)
{
$currentTestModule.TestClasses.Add($currentTestClass)
}
if ($currentTestModule -ne $null)
{
$testModules.Add($currentTestModule)
}
return $testModules
}
Write-Verbose "TaefQuery = $TaefQuery"
$TaefSelectQuery = ""
$TaefQueryToAppend = ""
if($TaefQuery)
{
$TaefSelectQuery = "/select:`"$TaefQuery`""
$TaefQueryToAppend = " and $TaefQuery"
}
Write-Verbose "TaefSelectQuery = $TaefSelectQuery"
$taefExe = "$TaefPath\te.exe"
[string]$taefOutput = & "$taefExe" /listproperties $TaefSelectQuery $TestFile | Out-String
[System.Collections.Generic.List[TestModule]]$testModules = (Parse-TestInfo $taefOutput)
$projFileContent = @"
<Project>
<ItemGroup>
"@
foreach ($testModule in $testModules)
{
foreach ($testClass in $testModules.TestClasses)
{
Write-Host "Generating Helix work item for test class $($testClass.Name)..."
[System.Collections.Generic.List[string]]$testSuiteNames = @()
$testSuiteExists = $false
$suitelessTestExists = $false
foreach ($test in $testClass.Tests)
{
# A test method inherits its 'TestSuite' property from its TestClass
if (!$test.Properties.ContainsKey("TestSuite") -and $testClass.Properties.ContainsKey("TestSuite"))
{
$test.Properties["TestSuite"] = $testClass.Properties["TestSuite"]
}
if ($test.Properties.ContainsKey("TestSuite"))
{
[string]$testSuite = $test.Properties["TestSuite"]
if (-not $testSuiteNames.Contains($testSuite))
{
Write-Host " Found test suite $testSuite. Generating Helix work item for it as well."
$testSuiteNames.Add($testSuite)
}
$testSuiteExists = $true
}
else
{
$suitelessTestExists = $true
}
}
$testClassSelectPattern = "$($testClass.Name).*"
if($testClass.Name.Contains("::"))
{
$testClassSelectPattern = "$($testClass.Name)::*"
}
$testNameQuery= "(@Name='$testClassSelectPattern')"
$workItemName = $testClass.Name
# Native tests use '::' as a separator, which is not valid for workItem names.
$workItemName = $workItemName -replace "::", "-"
if ($suitelessTestExists)
{
$projFileContent += @"
<HelixWorkItem Include="$($workItemName)" Condition="'`$(TestSuite)'=='$($JobTestSuiteName)'">
<Timeout>00:30:00</Timeout>
<Command>call %HELIX_CORRELATION_PAYLOAD%\runtests.cmd /select:"(@Name='$($testClass.Name)*'$(if ($testSuiteExists) { "and not @TestSuite='*'" }))$($TaefQueryToAppend)"</Command>
</HelixWorkItem>
"@
}
foreach ($testSuiteName in $testSuiteNames)
{
$projFileContent += @"
<HelixWorkItem Include="$($workItemName)-$testSuiteName" Condition="'`$(TestSuite)'=='$($JobTestSuiteName)'">
<Timeout>00:30:00</Timeout>
<Command>call %HELIX_CORRELATION_PAYLOAD%\runtests.cmd /select:"(@Name='$($testClass.Name)*' and @TestSuite='$testSuiteName')$($TaefQueryToAppend)"</Command>
</HelixWorkItem>
"@
}
}
}
$projFileContent += @"
</ItemGroup>
</Project>
"@
Set-Content $OutputProjFile $projFileContent -NoNewline -Encoding UTF8

View File

@@ -0,0 +1,669 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml.Linq;
namespace HelixTestHelpers
{
public class TestResult
{
public TestResult()
{
Screenshots = new List<string>();
RerunResults = new List<TestResult>();
}
public string Name { get; set; }
public string SourceWttFile { get; set; }
public bool Passed { get; set; }
public bool CleanupPassed { get; set; }
public TimeSpan ExecutionTime { get; set; }
public string Details { get; set; }
public List<string> Screenshots { get; private set; }
public List<TestResult> RerunResults { get; private set; }
// Returns true if the test pass rate is sufficient to avoid being counted as a failure.
public bool PassedOrUnreliable(int requiredNumberOfPasses)
{
if(Passed)
{
return true;
}
else
{
if(RerunResults.Count == 1)
{
return RerunResults[0].Passed;
}
else
{
return RerunResults.Where(r => r.Passed).Count() >= requiredNumberOfPasses;
}
}
}
}
//
// Azure DevOps doesn't currently provide a way to directly report sub-results for tests that failed at least once
// that were run multiple times. To get around that limitation, we'll mark the test as "Skip" since
// that's the only non-pass/fail result we can return, and will then report the information about the
// runs in the "reason" category for the skipped test. In order to save space, we'll make the following
// optimizations for size:
//
// 1. Serialize as JSON, which is more compact than XML;
// 2. Don't serialize values that we don't need;
// 3. Store the URL prefix and suffix for the blob storage URL only once instead of
// storing every log and screenshot URL in its entirety; and
// 4. Store a list of unique error messages and then index into that instead of
// storing every error message in its entirety.
//
// #4 is motivated by the fact that if a test fails multiple times, it probably failed for the same reason
// each time, in which case we'd just be repeating ourselves if we stored every error message each time.
//
// TODO (https://github.com/dotnet/arcade/issues/2773): Once we're able to directly report things in a
// more granular fashion than just a binary pass/fail result, we should do that.
//
[DataContract]
internal class JsonSerializableTestResults
{
[DataMember]
internal string blobPrefix;
[DataMember]
internal string blobSuffix;
[DataMember]
internal string[] errors;
[DataMember]
internal JsonSerializableTestResult[] results;
}
[DataContract]
internal class JsonSerializableTestResult
{
[DataMember]
internal string outcome;
[DataMember]
internal int duration;
[DataMember(EmitDefaultValue = false)]
internal string log;
[DataMember(EmitDefaultValue = false)]
internal string[] screenshots;
[DataMember(EmitDefaultValue = false)]
internal int errorIndex;
}
public class TestPass
{
public TimeSpan TestPassExecutionTime { get; set; }
public List<TestResult> TestResults { get; set; }
public static TestPass ParseTestWttFile(string fileName, bool cleanupFailuresAreRegressions, bool truncateTestNames)
{
using (var stream = File.OpenRead(fileName))
{
var doc = XDocument.Load(stream);
var testResults = new List<TestResult>();
var testExecutionTimeMap = new Dictionary<string, List<double>>();
TestResult currentResult = null;
long frequency = 0;
long startTime = 0;
long stopTime = 0;
bool inTestCleanup = false;
bool shouldLogToTestDetails = false;
long testPassStartTime = 0;
long testPassStopTime = 0;
Func<XElement, bool> isScopeData = (elt) =>
{
return
elt.Element("Data") != null &&
elt.Element("Data").Element("WexContext") != null &&
(
elt.Element("Data").Element("WexContext").Value == "Cleanup" ||
elt.Element("Data").Element("WexContext").Value == "TestScope" ||
elt.Element("Data").Element("WexContext").Value == "TestScope" ||
elt.Element("Data").Element("WexContext").Value == "ClassScope" ||
elt.Element("Data").Element("WexContext").Value == "ModuleScope"
);
};
Func<XElement, bool> isModuleOrClassScopeStart = (elt) =>
{
return
elt.Name == "Msg" &&
elt.Element("Data") != null &&
elt.Element("Data").Element("StartGroup") != null &&
elt.Element("Data").Element("WexContext") != null &&
(elt.Element("Data").Element("WexContext").Value == "ClassScope" ||
elt.Element("Data").Element("WexContext").Value == "ModuleScope");
};
Func<XElement, bool> isModuleScopeEnd = (elt) =>
{
return
elt.Name == "Msg" &&
elt.Element("Data") != null &&
elt.Element("Data").Element("EndGroup") != null &&
elt.Element("Data").Element("WexContext") != null &&
elt.Element("Data").Element("WexContext").Value == "ModuleScope";
};
Func<XElement, bool> isClassScopeEnd = (elt) =>
{
return
elt.Name == "Msg" &&
elt.Element("Data") != null &&
elt.Element("Data").Element("EndGroup") != null &&
elt.Element("Data").Element("WexContext") != null &&
elt.Element("Data").Element("WexContext").Value == "ClassScope";
};
int testsExecuting = 0;
foreach (XElement element in doc.Root.Elements())
{
// Capturing the frequency data to record accurate
// timing data.
if (element.Name == "RTI")
{
frequency = Int64.Parse(element.Attribute("Frequency").Value);
}
// It's possible for a test to launch another test. If that happens, we won't modify the
// current result. Instead, we'll continue operating like normal and expect that we get two
// EndTests nodes before our next StartTests. We'll check that we've actually got a stop time
// before creating a new result. This will result in the two results being squashed
// into one result of the outer test that ran the inner one.
if (element.Name == "StartTest")
{
testsExecuting++;
if (testsExecuting == 1)
{
string testName = element.Attribute("Title").Value;
if (truncateTestNames)
{
const string xamlNativePrefix = "Windows::UI::Xaml::Tests::";
const string xamlManagedPrefix = "Windows.UI.Xaml.Tests.";
if (testName.StartsWith(xamlNativePrefix))
{
testName = testName.Substring(xamlNativePrefix.Length);
}
else if (testName.StartsWith(xamlManagedPrefix))
{
testName = testName.Substring(xamlManagedPrefix.Length);
}
}
currentResult = new TestResult() { Name = testName, SourceWttFile = fileName, Passed = true, CleanupPassed = true };
testResults.Add(currentResult);
startTime = Int64.Parse(element.Descendants("WexTraceInfo").First().Attribute("TimeStamp").Value);
inTestCleanup = false;
shouldLogToTestDetails = true;
stopTime = 0;
}
}
else if (currentResult != null && element.Name == "EndTest")
{
testsExecuting--;
// If any inner test fails, we'll still fail the outer
currentResult.Passed &= element.Attribute("Result").Value == "Pass";
// Only gather execution data if this is the outer test we ran initially
if (testsExecuting == 0)
{
stopTime = Int64.Parse(element.Descendants("WexTraceInfo").First().Attribute("TimeStamp").Value);
if (!testExecutionTimeMap.Keys.Contains(currentResult.Name))
testExecutionTimeMap[currentResult.Name] = new List<double>();
testExecutionTimeMap[currentResult.Name].Add((double)(stopTime - startTime) / frequency);
currentResult.ExecutionTime = TimeSpan.FromSeconds(testExecutionTimeMap[currentResult.Name].Average());
startTime = 0;
inTestCleanup = true;
}
}
else if (currentResult != null &&
(isModuleOrClassScopeStart(element) || isModuleScopeEnd(element) || isClassScopeEnd(element)))
{
shouldLogToTestDetails = false;
inTestCleanup = false;
}
// Log-appending methods.
if (currentResult != null && element.Name == "Error")
{
if (shouldLogToTestDetails)
{
currentResult.Details += "\r\n[Error]: " + element.Attribute("UserText").Value;
if (element.Attribute("File") != null && element.Attribute("File").Value != "")
{
currentResult.Details += (" [File " + element.Attribute("File").Value);
if (element.Attribute("Line") != null)
currentResult.Details += " Line: " + element.Attribute("Line").Value;
currentResult.Details += "]";
}
}
// The test cleanup errors will often come after the test claimed to have
// 'passed'. We treat them as errors as well.
if (inTestCleanup)
{
currentResult.CleanupPassed = false;
currentResult.Passed = false;
// In stress mode runs, this test will run n times before cleanup is run. If the cleanup
// fails, we want to fail every test.
if (cleanupFailuresAreRegressions)
{
foreach (var result in testResults.Where(res => res.Name == currentResult.Name))
{
result.Passed = false;
result.CleanupPassed = false;
}
}
}
}
if (currentResult != null && element.Name == "Warn")
{
if (shouldLogToTestDetails)
{
currentResult.Details += "\r\n[Warn]: " + element.Attribute("UserText").Value;
}
if (element.Attribute("File") != null && element.Attribute("File").Value != "")
{
currentResult.Details += (" [File " + element.Attribute("File").Value);
if (element.Attribute("Line") != null)
currentResult.Details += " Line: " + element.Attribute("Line").Value;
currentResult.Details += "]";
}
}
if (currentResult != null && element.Name == "Msg")
{
var dataElement = element.Element("Data");
if (dataElement != null)
{
var supportingInfo = dataElement.Element("SupportingInfo");
if (supportingInfo != null)
{
var screenshots = supportingInfo.Elements("Item")
.Where(item => GetAttributeValue(item, "Name") == "Screenshot")
.Select(item => GetAttributeValue(item, "Value"));
foreach(var screenshot in screenshots)
{
string fileNameSuffix = string.Empty;
if (fileName.Contains("_rerun_multiple"))
{
fileNameSuffix = "_rerun_multiple";
}
else if (fileName.Contains("_rerun"))
{
fileNameSuffix = "_rerun";
}
currentResult.Screenshots.Add(screenshot.Replace(".jpg", fileNameSuffix + ".jpg"));
}
}
}
}
}
testPassStartTime = Int64.Parse(doc.Root.Descendants("WexTraceInfo").First().Attribute("TimeStamp").Value);
testPassStopTime = Int64.Parse(doc.Root.Descendants("WexTraceInfo").Last().Attribute("TimeStamp").Value);
var testPassTime = TimeSpan.FromSeconds((double)(testPassStopTime - testPassStartTime) / frequency);
foreach (TestResult testResult in testResults)
{
if (testResult.Details != null)
{
testResult.Details = testResult.Details.Trim();
}
}
var testpass = new TestPass
{
TestPassExecutionTime = testPassTime,
TestResults = testResults
};
return testpass;
}
}
public static TestPass ParseTestWttFileWithReruns(string fileName, string singleRerunFileName, string multipleRerunFileName, bool cleanupFailuresAreRegressions, bool truncateTestNames)
{
TestPass testPass = ParseTestWttFile(fileName, cleanupFailuresAreRegressions, truncateTestNames);
TestPass singleRerunTestPass = File.Exists(singleRerunFileName) ? ParseTestWttFile(singleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null;
TestPass multipleRerunTestPass = File.Exists(multipleRerunFileName) ? ParseTestWttFile(multipleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null;
List<TestResult> rerunTestResults = new List<TestResult>();
if (singleRerunTestPass != null)
{
rerunTestResults.AddRange(singleRerunTestPass.TestResults);
}
if (multipleRerunTestPass != null)
{
rerunTestResults.AddRange(multipleRerunTestPass.TestResults);
}
// For each failed test result, we'll check to see whether the test passed at least once upon rerun.
// If so, we'll set PassedOnRerun to true to flag the fact that this is an unreliable test
// rather than a genuine test failure.
foreach (TestResult failedTestResult in testPass.TestResults.Where(r => !r.Passed))
{
failedTestResult.RerunResults.AddRange(rerunTestResults.Where(r => r.Name == failedTestResult.Name));
}
return testPass;
}
private static string GetAttributeValue(XElement element, string attributeName)
{
if(element.Attribute(attributeName) != null)
{
return element.Attribute(attributeName).Value;
}
return null;
}
}
public static class FailedTestDetector
{
public static void OutputFailedTestQuery(string wttInputPath)
{
var testPass = TestPass.ParseTestWttFile(wttInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false);
List<string> failedTestNames = new List<string>();
foreach (var result in testPass.TestResults)
{
if (!result.Passed)
{
failedTestNames.Add(result.Name);
}
}
if (failedTestNames.Count > 0)
{
string failedTestSelectQuery = "(@Name='";
for (int i = 0; i < failedTestNames.Count; i++)
{
failedTestSelectQuery += failedTestNames[i];
if (i < failedTestNames.Count - 1)
{
failedTestSelectQuery += "' or @Name='";
}
}
failedTestSelectQuery += "')";
Console.WriteLine(failedTestSelectQuery);
}
else
{
Console.WriteLine("");
}
}
}
public class TestResultParser
{
private string testNamePrefix;
private string helixResultsContainerUri;
private string helixResultsContainerRsas;
public TestResultParser(string testNamePrefix, string helixResultsContainerUri, string helixResultsContainerRsas)
{
this.testNamePrefix = testNamePrefix;
this.helixResultsContainerUri = helixResultsContainerUri;
this.helixResultsContainerRsas = helixResultsContainerRsas;
}
public Dictionary<string, string> GetSubResultsJsonByMethodName(string wttInputPath, string wttSingleRerunInputPath, string wttMultipleRerunInputPath)
{
Dictionary<string, string> subResultsJsonByMethod = new Dictionary<string, string>();
TestPass testPass = TestPass.ParseTestWttFileWithReruns(wttInputPath, wttSingleRerunInputPath, wttMultipleRerunInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false);
foreach (var result in testPass.TestResults)
{
var methodName = result.Name.Substring(result.Name.LastIndexOf('.') + 1);
if (!result.Passed)
{
// If a test failed, we'll have rerun it multiple times. We'll record the results of each run
// formatted as JSON.
JsonSerializableTestResults serializableResults = new JsonSerializableTestResults();
serializableResults.blobPrefix = helixResultsContainerUri;
serializableResults.blobSuffix = helixResultsContainerRsas;
List<string> errorList = new List<string>();
errorList.Add(result.Details);
foreach (TestResult rerunResult in result.RerunResults)
{
errorList.Add(rerunResult.Details);
}
serializableResults.errors = errorList.Distinct().Where(s => s != null).ToArray();
var reason = new XElement("reason");
List<JsonSerializableTestResult> serializableResultList = new List<JsonSerializableTestResult>();
serializableResultList.Add(ConvertToSerializableResult(result, serializableResults.errors));
foreach (TestResult rerunResult in result.RerunResults)
{
serializableResultList.Add(ConvertToSerializableResult(rerunResult, serializableResults.errors));
}
serializableResults.results = serializableResultList.ToArray();
using (MemoryStream stream = new MemoryStream())
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(JsonSerializableTestResults));
serializer.WriteObject(stream, serializableResults);
stream.Position = 0;
using (StreamReader streamReader = new StreamReader(stream))
{
subResultsJsonByMethod.Add(methodName, streamReader.ReadToEnd());
}
}
}
}
return subResultsJsonByMethod;
}
public void ConvertWttLogToXUnitLog(string wttInputPath, string wttSingleRerunInputPath, string wttMultipleRerunInputPath, string xunitOutputPath, int requiredPassRateThreshold)
{
TestPass testPass = TestPass.ParseTestWttFileWithReruns(wttInputPath, wttSingleRerunInputPath, wttMultipleRerunInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false);
var results = testPass.TestResults;
int resultCount = results.Count;
int passedCount = results.Where(r => r.Passed).Count();
// Since we re-run tests on failure, we'll mark every test that failed at least once as "skipped" rather than "failed".
// If the test failed sufficiently often enough for it to count as a failed test (determined by a property on the
// Azure DevOps job), we'll later mark it as failed during test results processing.
int failedCount = results.Where(r => !r.PassedOrUnreliable(requiredPassRateThreshold)).Count();
int skippedCount = results.Where(r => !r.Passed && r.PassedOrUnreliable(requiredPassRateThreshold)).Count();
var root = new XElement("assemblies");
var assembly = new XElement("assembly");
assembly.SetAttributeValue("name", "MUXControls.Test.dll");
assembly.SetAttributeValue("test-framework", "TAEF");
assembly.SetAttributeValue("run-date", DateTime.Now.ToString("yyyy-MM-dd"));
// This doesn't need to be completely accurate since it's not exposed anywhere.
// If we need accurate an start time we can probably calculate it from the te.wtl file, but for
// now this is fine.
assembly.SetAttributeValue("run-time", (DateTime.Now - testPass.TestPassExecutionTime).ToString("hh:mm:ss"));
assembly.SetAttributeValue("total", resultCount);
assembly.SetAttributeValue("passed", passedCount);
assembly.SetAttributeValue("failed", failedCount);
assembly.SetAttributeValue("skipped", skippedCount);
assembly.SetAttributeValue("time", (int)testPass.TestPassExecutionTime.TotalSeconds);
assembly.SetAttributeValue("errors", 0);
root.Add(assembly);
var collection = new XElement("collection");
collection.SetAttributeValue("total", resultCount);
collection.SetAttributeValue("passed", passedCount);
collection.SetAttributeValue("failed", failedCount);
collection.SetAttributeValue("skipped", skippedCount);
collection.SetAttributeValue("name", "Test collection");
collection.SetAttributeValue("time", (int)testPass.TestPassExecutionTime.TotalSeconds);
assembly.Add(collection);
foreach (var result in results)
{
var test = new XElement("test");
test.SetAttributeValue("name", testNamePrefix + "." + result.Name);
var className = GetTestClassName(result.Name);
var methodName = GetTestMethodName(result.Name);
test.SetAttributeValue("type", className);
test.SetAttributeValue("method", methodName);
test.SetAttributeValue("time", result.ExecutionTime.TotalSeconds);
string resultString = string.Empty;
if (result.Passed)
{
resultString = "Pass";
}
else if(result.PassedOrUnreliable(requiredPassRateThreshold))
{
resultString = "Skip";
}
else
{
resultString = "Fail";
}
test.SetAttributeValue("result", resultString);
if (!result.Passed)
{
// If a test failed, we'll have rerun it multiple times.
// We'll save the subresults to a JSON text file that we'll upload to the helix results container -
// this allows it to be as long as we want, whereas the reason field in Azure DevOps has a 4000 character limit.
string subResultsFileName = methodName + "_subresults.json";
string subResultsFilePath = Path.Combine(Path.GetDirectoryName(wttInputPath), subResultsFileName);
if (result.PassedOrUnreliable(requiredPassRateThreshold))
{
var reason = new XElement("reason");
reason.Add(new XCData(GetUploadedFileUrl(subResultsFileName, helixResultsContainerUri, helixResultsContainerRsas)));
test.Add(reason);
}
else
{
var failure = new XElement("failure");
var message = new XElement("message");
message.Add(new XCData(GetUploadedFileUrl(subResultsFileName, helixResultsContainerUri, helixResultsContainerRsas)));
failure.Add(message);
test.Add(failure);
}
}
collection.Add(test);
}
File.WriteAllText(xunitOutputPath, root.ToString());
}
private JsonSerializableTestResult ConvertToSerializableResult(TestResult rerunResult, string[] uniqueErrors)
{
var serializableResult = new JsonSerializableTestResult();
serializableResult.outcome = rerunResult.Passed ? "Passed" : "Failed";
serializableResult.duration = (int)Math.Round(rerunResult.ExecutionTime.TotalMilliseconds);
if (!rerunResult.Passed)
{
serializableResult.log = Path.GetFileName(rerunResult.SourceWttFile);
if (rerunResult.Screenshots.Any())
{
List<string> screenshots = new List<string>();
foreach (var screenshot in rerunResult.Screenshots)
{
screenshots.Add(Path.GetFileName(screenshot));
}
serializableResult.screenshots = screenshots.ToArray();
}
// To conserve space, we'll log the index of the error to index in a list of unique errors rather than
// jotting down every single error in its entirety. We'll add one to the result so we can avoid
// serializing this property when it has the default value of 0.
serializableResult.errorIndex = Array.IndexOf(uniqueErrors, rerunResult.Details) + 1;
}
return serializableResult;
}
private string GetUploadedFileUrl(string filePath, string helixResultsContainerUri, string helixResultsContainerRsas)
{
var filename = Path.GetFileName(filePath);
return string.Format("{0}/{1}{2}", helixResultsContainerUri, filename, helixResultsContainerRsas);
}
private string GetTestNameSeparator(string testname)
{
var separatorString = ".";
if (!testname.Contains(separatorString))
{
separatorString = "::";
}
return separatorString;
}
private string GetTestMethodName(string fullyQualifiedName)
{
var separatorString = GetTestNameSeparator(fullyQualifiedName);
var methodName = fullyQualifiedName.Substring(fullyQualifiedName.LastIndexOf(separatorString) + separatorString.Length);
return methodName;
}
private string GetTestClassName(string fullyQualifiedName)
{
var separatorString = GetTestNameSeparator(fullyQualifiedName);
var className = fullyQualifiedName.Substring(0, fullyQualifiedName.LastIndexOf(separatorString));
return className;
}
}
}

View File

@@ -0,0 +1,12 @@
# Displaying progress is unnecessary and is just distracting.
$ProgressPreference = "SilentlyContinue"
$dependencyFiles = Get-ChildItem -Filter "*Microsoft.VCLibs.*.appx"
foreach ($file in $dependencyFiles)
{
Write-Host "Adding dependency $($file)..."
Add-AppxPackage $file
}

View File

@@ -0,0 +1,8 @@
Param(
[Parameter(Mandatory = $true)]
[string]$WttInputPath
)
Add-Type -Language CSharp -ReferencedAssemblies System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Runtime.Serialization.Json (Get-Content $PSScriptRoot\HelixTestHelpers.cs -Raw)
[HelixTestHelpers.FailedTestDetector]::OutputFailedTestQuery($WttInputPath)

View File

@@ -0,0 +1,32 @@
Param(
[Parameter(Mandatory = $true)]
[string]$WttInputPath,
[Parameter(Mandatory = $true)]
[string]$WttSingleRerunInputPath,
[Parameter(Mandatory = $true)]
[string]$WttMultipleRerunInputPath,
[Parameter(Mandatory = $true)]
[string]$TestNamePrefix
)
# Ideally these would be passed as parameters to the script. However ps makes it difficult to deal with string literals containing '&', so we just
# read the values directly from the environment variables
$helixResultsContainerUri = $Env:HELIX_RESULTS_CONTAINER_URI
$helixResultsContainerRsas = $Env:HELIX_RESULTS_CONTAINER_RSAS
Add-Type -Language CSharp -ReferencedAssemblies System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Runtime.Serialization.Json (Get-Content $PSScriptRoot\HelixTestHelpers.cs -Raw)
$testResultParser = [HelixTestHelpers.TestResultParser]::new($TestNamePrefix, $helixResultsContainerUri, $helixResultsContainerRsas)
[System.Collections.Generic.Dictionary[string, string]]$subResultsJsonByMethodName = $testResultParser.GetSubResultsJsonByMethodName($WttInputPath, $WttSingleRerunInputPath, $WttMultipleRerunInputPath)
$subResultsJsonDirectory = [System.IO.Path]::GetDirectoryName($WttInputPath)
foreach ($methodName in $subResultsJsonByMethodName.Keys)
{
$subResultsJson = $subResultsJsonByMethodName[$methodName]
$subResultsJsonPath = [System.IO.Path]::Combine($subResultsJsonDirectory, $methodName + "_subresults.json")
Out-File $subResultsJsonPath -Encoding utf8 -InputObject $subResultsJson
}

View File

@@ -0,0 +1,131 @@
Param(
[Parameter(Mandatory = $true)]
[int]$MinimumExpectedTestsExecutedCount,
[string]$AccessToken = $env:SYSTEM_ACCESSTOKEN,
[string]$CollectionUri = $env:SYSTEM_COLLECTIONURI,
[string]$TeamProject = $env:SYSTEM_TEAMPROJECT,
[string]$BuildUri = $env:BUILD_BUILDURI,
[bool]$CheckJobAttempt
)
$azureDevOpsRestApiHeaders = @{
"Accept"="application/json"
"Authorization"="Basic $([System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes(":$AccessToken")))"
}
. "$PSScriptRoot/AzurePipelinesHelperScripts.ps1"
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
[System.Collections.Generic.List[string]]$failingTests = @()
[System.Collections.Generic.List[string]]$unreliableTests = @()
[System.Collections.Generic.List[string]]$unexpectedResultTest = @()
[System.Collections.Generic.List[string]]$namesOfProcessedTestRuns = @()
$totalTestsExecutedCount = 0
# We assume that we only have one testRun with a given name that we care about
# We only process the last testRun with a given name (based on completedDate)
# The name of a testRun is set to the Helix queue that it was run on (e.g. windows.10.amd64.client19h1.xaml)
# If we have multiple test runs on the same queue that we care about, we will need to re-visit this logic
foreach ($testRun in ($testRuns.value | Sort-Object -Property "completedDate" -Descending))
{
if ($CheckJobAttempt)
{
if ($namesOfProcessedTestRuns -contains $testRun.name)
{
Write-Host "Skipping test run '$($testRun.name)', since we have already processed a test run of that name."
continue
}
}
Write-Host "Processing results from test run '$($testRun.name)'"
$namesOfProcessedTestRuns.Add($testRun.name)
$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
foreach ($testResult in $testResults.value)
{
$shortTestCaseTitle = $testResult.testCaseTitle -replace "[a-zA-Z0-9]+.[a-zA-Z0-9]+.Windows.UI.Xaml.Tests.MUXControls.",""
if ($testResult.outcome -eq "Failed")
{
if (-not $failingTests.Contains($shortTestCaseTitle))
{
$failingTests.Add($shortTestCaseTitle)
}
}
elseif ($testResult.outcome -eq "Warning")
{
if (-not $unreliableTests.Contains($shortTestCaseTitle))
{
$unreliableTests.Add($shortTestCaseTitle)
}
}
elseif ($testResult.outcome -ne "Passed")
{
# We should only see tests with result "Passed", "Failed" or "Warning"
if (-not $unexpectedResultTest.Contains($shortTestCaseTitle))
{
$unexpectedResultTest.Add($shortTestCaseTitle)
}
}
}
}
if ($unreliableTests.Count -gt 0)
{
Write-Host @"
##vso[task.logissue type=warning;]Unreliable tests:
##vso[task.logissue type=warning;]$($unreliableTests -join "$([Environment]::NewLine)##vso[task.logissue type=warning;]")
"@
}
if ($failingTests.Count -gt 0)
{
Write-Host @"
##vso[task.logissue type=error;]Failing tests:
##vso[task.logissue type=error;]$($failingTests -join "$([Environment]::NewLine)##vso[task.logissue type=error;]")
"@
}
if ($unexpectedResultTest.Count -gt 0)
{
Write-Host @"
##vso[task.logissue type=error;]Tests with unexpected results:
##vso[task.logissue type=error;]$($unexpectedResultTest -join "$([Environment]::NewLine)##vso[task.logissue type=error;]")
"@
}
if($totalTestsExecutedCount -lt $MinimumExpectedTestsExecutedCount)
{
Write-Host "Expected at least $MinimumExpectedTestsExecutedCount tests to be executed."
Write-Host "Actual executed test count is: $totalTestsExecutedCount"
Write-Host "##vso[task.complete result=Failed;]"
}
elseif ($failingTests.Count -gt 0)
{
Write-Host "At least one test failed."
Write-Host "##vso[task.complete result=Failed;]"
}
elseif ($unreliableTests.Count -gt 0)
{
Write-Host "All tests eventually passed, but some initially failed."
Write-Host "##vso[task.complete result=Succeeded;]"
}
else
{
Write-Host "All tests passed."
Write-Host "##vso[task.complete result=Succeeded;]"
}

View File

@@ -0,0 +1,54 @@
[CmdLetBinding()]
Param(
[string]$Platform,
[string]$Configuration,
[string]$ArtifactName='drop'
)
$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)
{
Remove-Item $payloadDir -Recurse
}
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\taef.redist.wlk.10.57.200731005-develop\build\Binaries\$Platform\*" $payloadDir
Copy-Item "$nugetPackagesDir\taef.redist.wlk.10.57.200731005-develop\build\Binaries\$Platform\CoreClr\*" $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\"
function Copy-If-Exists
{
Param($source, $destinationDir)
if (Test-Path $source)
{
Write-Host "Copy from '$source' to '$destinationDir'"
Copy-Item -Force $source $destinationDir
}
else
{
Write-Host "'$source' does not exist."
}
}
# Copy files from the 'drop' artifact dir
Copy-Item "$repoDirectory\Artifacts\$ArtifactName\$Configuration\$Platform\Test\*" $payloadDir -Recurse
# Copy files from the repo
New-Item -ItemType Directory -Force -Path "$payloadDir"
Copy-Item "build\helix\ConvertWttLogToXUnit.ps1" "$payloadDir"
Copy-Item "build\helix\OutputFailedTestQuery.ps1" "$payloadDir"
Copy-Item "build\helix\OutputSubResultsJsonFiles.ps1" "$payloadDir"
Copy-Item "build\helix\HelixTestHelpers.cs" "$payloadDir"
Copy-Item "build\helix\runtests.cmd" $payloadDir
Copy-Item "build\helix\InstallTestAppDependencies.ps1" "$payloadDir"
Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"

View File

@@ -0,0 +1,112 @@
Param(
[string]$AccessToken = $env:SYSTEM_ACCESSTOKEN,
[string]$HelixAccessToken = $env:HelixAccessToken,
[string]$CollectionUri = $env:SYSTEM_COLLECTIONURI,
[string]$TeamProject = $env:SYSTEM_TEAMPROJECT,
[string]$BuildUri = $env:BUILD_BUILDURI,
[string]$OutputFolder = "HelixOutput"
)
$helixLinkFile = "$OutputFolder\LinksToHelixTestFiles.html"
$accessTokenParam = ""
if($HelixAccessToken)
{
$accessTokenParam = "?access_token=$HelixAccessToken"
}
function Generate-File-Links
{
Param ([Array[]]$files,[string]$sectionName)
if($files.Count -gt 0)
{
Out-File -FilePath $helixLinkFile -Append -InputObject "<div class=$sectionName>"
Out-File -FilePath $helixLinkFile -Append -InputObject "<h4>$sectionName</h4>"
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>"
}
Out-File -FilePath $helixLinkFile -Append -InputObject "</ul>"
Out-File -FilePath $helixLinkFile -Append -InputObject "</div>"
}
}
#Create output directory
New-Item $OutputFolder -ItemType Directory
$azureDevOpsRestApiHeaders = @{
"Accept"="application/json"
"Authorization"="Basic $([System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes(":$AccessToken")))"
}
. "$PSScriptRoot/AzurePipelinesHelperScripts.ps1"
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri -IncludeRunDetails
Write-Host "queryUri = $queryUri"
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
$webClient = New-Object System.Net.WebClient
[System.Collections.Generic.List[string]]$workItems = @()
foreach ($testRun in $testRuns.value)
{
$testResults = Invoke-RestMethod -Uri "$($testRun.url)/results?api-version=5.0" -Method Get -Headers $azureDevOpsRestApiHeaders
$isTestRunNameShown = $false
foreach ($testResult in $testResults.value)
{
if ("comment" -in $testResult)
{
$info = ConvertFrom-Json $testResult.comment
$helixJobId = $info.HelixJobId
$helixWorkItemName = $info.HelixWorkItemName
$workItem = "$helixJobId-$helixWorkItemName"
if (-not $workItems.Contains($workItem))
{
$workItems.Add($workItem)
$filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files$accessTokenParam"
$files = Invoke-RestMethod -Uri $filesQueryUri -Method Get
$screenShots = $files | where { $_.Name.EndsWith(".jpg") }
$dumps = $files | where { $_.Name.EndsWith(".dmp") }
$pgcFiles = $files | where { $_.Name.EndsWith(".pgc") }
if ($screenShots.Count + $dumps.Count + $pgcFiles.Count -gt 0)
{
if(-Not $isTestRunNameShown)
{
Out-File -FilePath $helixLinkFile -Append -InputObject "<h2>$($testRun.name)</h2>"
$isTestRunNameShown = $true
}
Out-File -FilePath $helixLinkFile -Append -InputObject "<h3>$helixWorkItemName</h3>"
Generate-File-Links $screenShots "Screenshots"
Generate-File-Links $dumps "CrashDumps"
Generate-File-Links $pgcFiles "PGC files"
$misc = $files | where { ($screenShots -NotContains $_) -And ($dumps -NotContains $_) -And ($visualTreeVerificationFiles -NotContains $_) -And ($pgcFiles -NotContains $_) }
Generate-File-Links $misc "Misc"
foreach($pgcFile in $pgcFiles)
{
$flavorPath = $pgcFile.Name.Split('.')[0]
$archPath = $pgcFile.Name.Split('.')[1]
$fileName = $pgcFile.Name.Remove(0, $flavorPath.length + $archPath.length + 2)
$fullPath = "$OutputFolder\PGO\$flavorPath\$archPath"
$destination = "$fullPath\$fileName"
Write-Host "Copying $($pgcFile.Name) to $destination"
if (-Not (Test-Path $fullPath))
{
New-Item $fullPath -ItemType Directory
}
$link = "$($pgcFile.Link)$accessTokenParam"
$webClient.DownloadFile($link, $destination)
}
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.DotNet.Helix.Sdk" DefaultTargets="Test">
<PropertyGroup>
<HelixSource>pr/terminal/$(BUILD_SOURCEBRANCH)/</HelixSource>
<EnableXUnitReporter>true</EnableXUnitReporter>
<EnableAzurePipelinesReporter>true</EnableAzurePipelinesReporter>
<FailOnMissionControlTestFailure>true</FailOnMissionControlTestFailure>
<HelixPreCommands>$(HelixPreCommands);set testnameprefix=$(Configuration).$(Platform);set testbuildplatform=$(Platform);set rerunPassesRequiredToAvoidFailure=$(rerunPassesRequiredToAvoidFailure)</HelixPreCommands>
<OutputPath>..\..\bin\$(Platform)\$(Configuration)\</OutputPath>
</PropertyGroup>
<ItemGroup>
<HelixCorrelationPayload Include="..\..\HelixPayload\$(Configuration)\$(Platform)" />
</ItemGroup>
<!-- These .proj files are generated by the build machine prior to running tests via GenerateTestProjFile.ps1. -->
<Import Project="$(ProjFilesPath)\RunTestsInHelix-TerminalAppLocalTests.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
<Import Project="$(ProjFilesPath)\RunTestsInHelix-HostTestsUIA.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
</Project>

View File

@@ -0,0 +1,135 @@
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $true)]
[int]$RerunPassesRequiredToAvoidFailure,
[string]$AccessToken = $env:SYSTEM_ACCESSTOKEN,
[string]$CollectionUri = $env:SYSTEM_COLLECTIONURI,
[string]$TeamProject = $env:SYSTEM_TEAMPROJECT,
[string]$BuildUri = $env:BUILD_BUILDURI
)
. "$PSScriptRoot/AzurePipelinesHelperScripts.ps1"
$azureDevOpsRestApiHeaders = @{
"Accept"="application/json"
"Authorization"="Basic $([System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes(":$AccessToken")))"
}
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri
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
$timesSeenByRunName = @{}
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
Write-Host "Retrieving test results..."
$testResults = Invoke-RestMethod -Uri $testRunResultsUri -Method Get -Headers $azureDevOpsRestApiHeaders
foreach ($testResult in $testResults.value)
{
$testNeedsSubResultProcessing = $false
if ($testResult.outcome -eq "NotExecuted")
{
$testNeedsSubResultProcessing = $true
}
elseif($testResult.outcome -eq "Failed")
{
$testNeedsSubResultProcessing = $testResult.errorMessage -like "*_subresults.json*"
}
if ($testNeedsSubResultProcessing)
{
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)
[System.Collections.Generic.List[System.Collections.Hashtable]]$rerunDataList = @()
$attemptCount = 0
$passCount = 0
$totalDuration = 0
foreach ($rerun in $rerunResults.results)
{
$rerunData = @{
"displayName" = "Attempt #$($attemptCount + 1) - $($testResult.testCaseTitle)";
"durationInMs" = $rerun.duration;
"outcome" = $rerun.outcome;
}
if ($rerun.outcome -eq "Passed")
{
$passCount++
}
if ($attemptCount -gt 0)
{
$rerunData["sequenceId"] = $attemptCount
}
Write-Host " Attempt #$($attemptCount + 1): $($rerun.outcome)"
if ($rerun.outcome -ne "Passed")
{
$screenshots = "$($rerunResults.blobPrefix)/$($rerun.screenshots -join @"
$($rerunResults.blobSuffix)
$($rerunResults.blobPrefix)
"@)$($rerunResults.blobSuffix)"
# We subtract 1 from the error index because we added 1 so we could use 0
# as a default value not injected into the JSON in order to keep its size down.
# We did this because there's a maximum size enforced for the errorMessage parameter
# in the Azure DevOps REST API.
$fullErrorMessage = @"
Log: $($rerunResults.blobPrefix)/$($rerun.log)$($rerunResults.blobSuffix)
Screenshots:
$screenshots
Error log:
$($rerunResults.errors[$rerun.errorIndex - 1])
"@
$rerunData["errorMessage"] = $fullErrorMessage
}
$attemptCount++
$totalDuration += $rerun.duration
$rerunDataList.Add($rerunData)
}
$overallOutcome = "Warning"
if ($attemptCount -eq 2)
{
Write-Host " Test $($testResult.testCaseTitle) passed on the immediate rerun, so we'll mark it as unreliable."
}
elseif ($passCount -gt $RerunPassesRequiredToAvoidFailure)
{
Write-Host " Test $($testResult.testCaseTitle) passed on $passCount of $attemptCount attempts, which is greater than or equal to the $RerunPassesRequiredToAvoidFailure passes required to avoid being marked as failed. Marking as unreliable."
}
else
{
Write-Host " Test $($testResult.testCaseTitle) passed on only $passCount of $attemptCount attempts, which is less than the $RerunPassesRequiredToAvoidFailure passes required to avoid being marked as failed. Marking as failed."
$overallOutcome = "Failed"
}
$updateBody = ConvertTo-Json @(@{ "id" = $testResult.id; "outcome" = $overallOutcome; "errorMessage" = " "; "durationInMs" = $totalDuration; "subResults" = $rerunDataList; "resultGroupType" = "rerun" }) -Depth 5
Invoke-RestMethod -Uri $testRunResultsUri -Method Patch -Headers $azureDevOpsRestApiHeaders -Body $updateBody -ContentType "application/json" | Out-Null
}
}
Write-Host "Finished updates. Re-marking test run `"$($testRun.name)`" as completed."
Invoke-RestMethod -Uri "$($testRun.url)?api-version=5.0" -Method Patch -Body (ConvertTo-Json @{ "state" = "Completed" }) -Headers $azureDevOpsRestApiHeaders -ContentType "application/json" | Out-Null
}

5
build/Helix/global.json Normal file
View File

@@ -0,0 +1,5 @@
{
"msbuild-sdks": {
"Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20277.5"
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
<package id="TAEF.Redist.Wlk" version="10.57.200731005-develop" 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" />
</packages>

32
build/Helix/readme.md Normal file
View File

@@ -0,0 +1,32 @@
This directory contains code and configuration files to run WinUI tests in Helix.
Helix is a cloud hosted test execution environment which is accessed via the Arcade SDK.
More details:
* [Arcade](https://github.com/dotnet/arcade)
* [Helix](https://github.com/dotnet/arcade/tree/master/src/Microsoft.DotNet.Helix/Sdk)
WinUI tests are scheduled in Helix by the Azure DevOps Pipeline: [RunHelixTests.yml](../RunHelixTests.yml).
The workflow is as follows:
1. NuGet Restore is called on the packages.config in this directory. This downloads any runtime dependencies
that are needed to run tests.
2. PrepareHelixPayload.ps1 is called. This copies the necessary files from various locations into a Helix
payload directory. This directory is what will get sent to the Helix machines.
3. RunTestsInHelix.proj is executed. This proj has a dependency on
[Microsoft.DotNet.Helix.Sdk](https://github.com/dotnet/arcade/tree/master/src/Microsoft.DotNet.Helix/Sdk)
which it uses to publish the Helix payload directory and to schedule the Helix Work Items. The WinUI tests
are parallelized into multiple Helix Work Items.
4. Each Helix Work Item calls [runtests.cmd](runtests.cmd) with a specific query to pass to
[TAEF](https://docs.microsoft.com/en-us/windows-hardware/drivers/taef/) which runs the tests.
5. If a test is detected to have failed, we run it again, first once, then eight more times if it fails again.
If it fails all ten times, we report the test as failed; otherwise, we report it as unreliable,
which will show up as a warning, but which will not fail the build. When a test is reported as unreliable,
we include the results for each individual run via a JSON string in the original test's errorMessage field.
6. TAEF produces logs in WTT format. Helix is able to process logs in XUnit format. We run
[ConvertWttLogToXUnit.ps1](ConvertWttLogToXUnit.ps1) to convert the logs into the necessary format.
7. RunTestsInHelix.proj has EnableAzurePipelinesReporter set to true. This allows the XUnit formatted test
results to be reported back to the Azure DevOps Pipeline.
8. We process unreliable tests once all tests have been reported by reading the JSON string from the
errorMessage field and calling the Azure DevOps REST API to modify the unreliable tests to have sub-results
added to the test and to mark the test as "warning", which will enable people to see exactly how the test
failed in runs where it did.

106
build/Helix/runtests.cmd Normal file
View File

@@ -0,0 +1,106 @@
setlocal ENABLEDELAYEDEXPANSION
echo %TIME%
robocopy %HELIX_CORRELATION_PAYLOAD% . /s /NP > NUL
echo %TIME%
reg add HKLM\Software\Policies\Microsoft\Windows\Appx /v AllowAllTrustedApps /t REG_DWORD /d 1 /f
rem enable dump collection for our test apps:
rem note, this script is run from a 32-bit cmd, but we need to set the native reg-key
FOR %%A IN (TestHostApp.exe,te.exe,te.processhost.exe,conhost.exe,OpenConsole.exe,WindowsTerminal.exe) DO (
%systemroot%\sysnative\cmd.exe /c reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\%%A" /v DumpFolder /t REG_EXPAND_SZ /d %HELIX_DUMP_FOLDER% /f
%systemroot%\sysnative\cmd.exe /c reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\%%A" /v DumpType /t REG_DWORD /d 2 /f
%systemroot%\sysnative\cmd.exe /c reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\%%A" /v DumpCount /t REG_DWORD /d 10 /f
)
echo %TIME%
:: kill dhandler, which is a tool designed to handle unexpected windows appearing. But since our tests are
:: expected to show UI we don't want it running.
taskkill -f -im dhandler.exe
echo %TIME%
powershell -ExecutionPolicy Bypass .\EnsureMachineState.ps1
echo %TIME%
powershell -ExecutionPolicy Bypass .\InstallTestAppDependencies.ps1
echo %TIME%
set testBinaryCandidates=TerminalApp.LocalTests.dll Conhost.UIA.Tests.dll
set testBinaries=
for %%B in (%testBinaryCandidates%) do (
if exist %%B (
set "testBinaries=!testBinaries! %%B"
)
)
echo %TIME%
te.exe %testBinaries% /enablewttlogging /unicodeOutput:false /sessionTimeout:0:15 /testtimeout:0:10 /screenCaptureOnError %*
echo %TIME%
powershell -ExecutionPolicy Bypass Get-Process
move te.wtl te_original.wtl
copy /y te_original.wtl %HELIX_WORKITEM_UPLOAD_ROOT%
copy /y WexLogFileOutput\*.jpg %HELIX_WORKITEM_UPLOAD_ROOT%
for /f "tokens=* delims=" %%a in ('dir /b *.pgc') do ren "%%a" "%testnameprefix%.%%~na.pgc"
copy /y *.pgc %HELIX_WORKITEM_UPLOAD_ROOT%
set FailedTestQuery=
for /F "tokens=* usebackq" %%I IN (`powershell -ExecutionPolicy Bypass .\OutputFailedTestQuery.ps1 te_original.wtl`) DO (
set FailedTestQuery=%%I
)
rem The first time, we'll just re-run failed tests once. In many cases, tests fail very rarely, such that
rem a single re-run will be sufficient to detect many unreliable tests.
if "%FailedTestQuery%" == "" goto :SkipReruns
echo %TIME%
te.exe %testBinaries% /enablewttlogging /unicodeOutput:false /sessionTimeout:0:15 /testtimeout:0:10 /screenCaptureOnError /select:"%FailedTestQuery%"
echo %TIME%
move te.wtl te_rerun.wtl
copy /y te_rerun.wtl %HELIX_WORKITEM_UPLOAD_ROOT%
copy /y WexLogFileOutput\*.jpg %HELIX_WORKITEM_UPLOAD_ROOT%
rem If there are still failing tests remaining, we'll run them eight more times, so they'll have been run a total of ten times.
rem If any tests fail all ten times, we can be pretty confident that these are actual test failures rather than unreliable tests.
if not exist te_rerun.wtl goto :SkipReruns
set FailedTestQuery=
for /F "tokens=* usebackq" %%I IN (`powershell -ExecutionPolicy Bypass .\OutputFailedTestQuery.ps1 te_rerun.wtl`) DO (
set FailedTestQuery=%%I
)
if "%FailedTestQuery%" == "" goto :SkipReruns
echo %TIME%
te.exe %testBinaries% /enablewttlogging /unicodeOutput:false /sessionTimeout:0:15 /testtimeout:0:10 /screenCaptureOnError /testmode:Loop /LoopTest:8 /select:"%FailedTestQuery%"
echo %TIME%
powershell -ExecutionPolicy Bypass Get-Process
move te.wtl te_rerun_multiple.wtl
copy /y te_rerun_multiple.wtl %HELIX_WORKITEM_UPLOAD_ROOT%
copy /y WexLogFileOutput\*.jpg %HELIX_WORKITEM_UPLOAD_ROOT%
powershell -ExecutionPolicy Bypass .\CopyVisualTreeVerificationFiles.ps1
:SkipReruns
powershell -ExecutionPolicy Bypass Get-Process
echo %TIME%
powershell -ExecutionPolicy Bypass .\OutputSubResultsJsonFiles.ps1 te_original.wtl te_rerun.wtl te_rerun_multiple.wtl %testnameprefix%
powershell -ExecutionPolicy Bypass .\ConvertWttLogToXUnit.ps1 te_original.wtl te_rerun.wtl te_rerun_multiple.wtl testResults.xml %testnameprefix%
echo %TIME%
copy /y *_subresults.json %HELIX_WORKITEM_UPLOAD_ROOT%
type testResults.xml
echo %TIME%

5
build/packages.config Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
<package id="TAEF.Redist.Wlk" version="10.57.200731005-develop" targetFramework="native" />
</packages>

View File

@@ -3,6 +3,7 @@ trigger:
branches:
include:
- master
- feature/*
paths:
exclude:
- doc/*
@@ -13,6 +14,7 @@ pr:
branches:
include:
- master
- feature/*
paths:
exclude:
- doc/*

View File

@@ -2,6 +2,8 @@ parameters:
configuration: 'Release'
platform: ''
additionalBuildArguments: ''
minimumExpectedTestsExecutedCount: 10 # Sanity check for minimum expected tests to be reported
rerunPassesRequiredToAvoidFailure: 5
jobs:
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
@@ -15,3 +17,19 @@ jobs:
- template: build-console-steps.yml
parameters:
additionalBuildArguments: ${{ parameters.additionalBuildArguments }}
- template: helix-runtests-job.yml
parameters:
name: 'RunTestsInHelix'
dependsOn: Build${{ parameters.platform }}${{ parameters.configuration }}
condition: and(succeeded(), and(eq('${{ parameters.platform }}', 'x64'), not(eq(variables['Build.Reason'], 'PullRequest'))))
testSuite: 'DevTestSuite'
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
- template: helix-processtestresults-job.yml
parameters:
dependsOn:
- RunTestsInHelix
condition: and(succeededOrFailed(), and(eq('${{ parameters.platform }}', 'x64'), not(eq(variables['Build.Reason'], 'PullRequest'))))
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
minimumExpectedTestsExecutedCount: ${{ parameters.minimumExpectedTestsExecutedCount }}

View File

@@ -1,5 +1,6 @@
parameters:
additionalBuildArguments: ''
testLogPath: '$(Build.BinariesDirectory)\$(BuildPlatform)\$(BuildConfiguration)\testsOnBuildMachine.wtl'
steps:
- checkout: self
@@ -7,23 +8,29 @@ steps:
clean: true
- task: NuGetToolInstaller@0
displayName: Ensure NuGet 4.8.1
displayName: 'Use NuGet 5.2.0'
inputs:
versionSpec: 4.8.1
- task: VisualStudioTestPlatformInstaller@1
displayName: Ensure VSTest Platform
versionSpec: 5.2.0
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
# This should be `task: NuGetCommand@2`
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
displayName: Restore NuGet packages
displayName: Restore NuGet packages for solution
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: OpenConsole.sln
restoreDirectory: '$(Build.SourcesDirectory)\packages'
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
displayName: Restore NuGet packages for extraneous build actions
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: build/packages.config
restoreDirectory: '$(Build.SourcesDirectory)\packages'
- task: VSBuild@1
displayName: 'Build solution **\OpenConsole.sln'
@@ -34,7 +41,9 @@ steps:
configuration: '$(BuildConfiguration)'
msbuildArgs: "${{ parameters.additionalBuildArguments }}"
clean: true
maximumCpuCount: true
# The build agents cannot currently support parallel build due to the
# memory requirements of our PCH files.
maximumCpuCount: false
- task: PowerShell@2
displayName: 'Check MSIX for common regressions'
@@ -66,7 +75,7 @@ steps:
inputs:
targetType: filePath
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
- task: PowerShell@2
@@ -74,9 +83,41 @@ steps:
inputs:
targetType: filePath
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
- task: PowerShell@2
displayName: 'Convert Test Logs from WTL to xUnit format'
inputs:
targetType: filePath
filePath: build\Helix\ConvertWttLogToXUnit.ps1
arguments: -WttInputPath '${{ parameters.testLogPath }}' -WttSingleRerunInputPath 'unused.wtl' -WttMultipleRerunInputPath 'unused2.wtl' -XUnitOutputPath 'onBuildMachineResults.xml' -TestNamePrefix '$(BuildConfiguration).$(BuildPlatform)'
condition: or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86'))
- task: PublishTestResults@2
displayName: 'Upload converted test logs'
inputs:
testResultsFormat: 'xUnit' # Options: JUnit, NUnit, VSTest, xUnit, cTest
testResultsFiles: '**/onBuildMachineResults.xml'
#searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
#mergeTestResults: false # Optional
#failTaskOnFailedTests: false # Optional
testRunTitle: 'On Build Machine Tests' # Optional
buildPlatform: $(BuildPlatform) # Optional
buildConfiguration: $(BuildConfiguration) # Optional
#publishRunAttachments: true # Optional
- task: CopyFiles@2
displayName: 'Copy result logs to Artifacts'
inputs:
Contents: |
**/*.wtl
**/*onBuildMachineResults.xml
${{ parameters.testLogPath }}
TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/$(BuildPlatform)/test'
OverWrite: true
flattenFolders: true
- task: CopyFiles@2
displayName: 'Copy *.appx/*.msix to Artifacts (Non-PR builds only)'
inputs:
@@ -90,9 +131,22 @@ steps:
flattenFolders: true
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact (appx) (Non-PR builds only)'
- task: CopyFiles@2
displayName: 'Copy outputs needed for test runs to Artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/appx'
ArtifactName: 'appx-$(BuildConfiguration)'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
Contents: |
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.exe
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.dll
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.xml
**/Microsoft.VCLibs.*.appx
**/TestHostApp/*
TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/$(BuildPlatform)/test'
OverWrite: true
flattenFolders: true
condition: and(and(succeeded(), eq(variables['BuildPlatform'], 'x64')), ne(variables['Build.Reason'], 'PullRequest'))
- task: PublishBuildArtifacts@1
displayName: 'Publish All Build Artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'

View File

@@ -0,0 +1,15 @@
parameters:
condition: ''
testFilePath: ''
outputProjFileName: ''
testSuite: ''
taefQuery: ''
steps:
- task: powershell@2
displayName: 'Create ${{ parameters.outputProjFileName }}'
condition: ${{ parameters.condition }}
inputs:
targetType: filePath
filePath: build\Helix\GenerateTestProjFile.ps1
arguments: -TestFile '${{ parameters.testFilePath }}' -OutputProjFile '$(Build.ArtifactStagingDirectory)\${{ parameters.outputProjFileName }}' -JobTestSuiteName '${{ parameters.testSuite }}' -TaefPath '$(Build.SourcesDirectory)\build\Helix\packages\taef.redist.wlk.10.57.200731005-develop\build\Binaries\x86' -TaefQuery '${{ parameters.taefQuery }}'

View File

@@ -0,0 +1,68 @@
parameters:
condition: 'succeededOrFailed()'
dependsOn: ''
rerunPassesRequiredToAvoidFailure: 5
minimumExpectedTestsExecutedCount: 10
checkJobAttempt: false
pgoArtifact: ''
jobs:
- job: ProcessTestResults
condition: ${{ parameters.condition }}
dependsOn: ${{ parameters.dependsOn }}
pool:
vmImage: 'windows-2019'
timeoutInMinutes: 120
variables:
helixOutputFolder: $(Build.SourcesDirectory)\HelixOutput
steps:
- task: powershell@2
displayName: 'UpdateUnreliableTests.ps1'
condition: succeededOrFailed()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
targetType: filePath
filePath: build\Helix\UpdateUnreliableTests.ps1
arguments: -RerunPassesRequiredToAvoidFailure '${{ parameters.rerunPassesRequiredToAvoidFailure }}'
- task: powershell@2
displayName: 'OutputTestResults.ps1'
condition: succeededOrFailed()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
targetType: filePath
filePath: build\Helix\OutputTestResults.ps1
arguments: -MinimumExpectedTestsExecutedCount '${{ parameters.minimumExpectedTestsExecutedCount }}' -CheckJobAttempt $${{ parameters.checkJobAttempt }}
- task: powershell@2
displayName: 'ProcessHelixFiles.ps1'
condition: succeededOrFailed()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
HelixAccessToken: $(HelixApiAccessToken)
inputs:
targetType: filePath
filePath: build\Helix\ProcessHelixFiles.ps1
arguments: -OutputFolder '$(helixOutputFolder)'
- ${{if ne(parameters.pgoArtifact, '') }}:
- script: move /y $(helixOutputFolder)\PGO $(Build.ArtifactStagingDirectory)
displayName: 'Move pgc files to PGO artifact'
- task: PublishBuildArtifacts@1
displayName: 'Publish Helix files'
condition: succeededOrFailed()
inputs:
PathtoPublish: $(helixOutputFolder)
artifactName: drop
- ${{if ne(parameters.pgoArtifact, '') }}:
- task: PublishBuildArtifacts@1
displayName: 'Publish pgc files'
condition: succeededOrFailed()
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)\PGO\Release
artifactName: ${{ parameters.pgoArtifact }}

View File

@@ -0,0 +1,131 @@
parameters:
name: 'RunTestsInHelix'
dependsOn: ''
condition: ''
testSuite: ''
# If a Pipeline runs this template more than once, this parameter should be unique per build flavor to differentiate the
# the different test runs:
helixType: 'test/devtest'
artifactName: 'drop'
maxParallel: 4
rerunPassesRequiredToAvoidFailure: 5
taefQuery: ''
# if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline:
useBuildOutputFromPipeline: $(System.DefinitionId)
matrix:
# Release_x86:
# buildPlatform: 'x86'
# buildConfiguration: 'release'
# openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml'
# closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml'
Release_x64:
buildPlatform: 'x64'
buildConfiguration: 'release'
openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml'
closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml'
jobs:
- job: ${{ parameters.name }}
dependsOn: ${{ parameters.dependsOn }}
condition: ${{ parameters.condition }}
pool:
vmImage: 'windows-2019'
timeoutInMinutes: 120
strategy:
maxParallel: ${{ parameters.maxParallel }}
matrix: ${{ parameters.matrix }}
variables:
artifactsDir: $(Build.SourcesDirectory)\Artifacts
taefPath: $(Build.SourcesDirectory)\build\Helix\packages\taef.redist.wlk.10.57.200731005-develop\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'
inputs:
filename: 'set'
- task: NuGetToolInstaller@0
displayName: 'Use NuGet 5.2.0'
inputs:
versionSpec: 5.2.0
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
displayName: 'NuGet restore build/Helix/packages.config'
inputs:
restoreSolution: build/Helix/packages.config
feedsToUse: config
nugetConfigPath: nuget.config
restoreDirectory: packages
- task: DownloadBuildArtifacts@0
condition:
and(succeeded(),eq(variables['useBuildOutputFromBuildId'],''))
inputs:
artifactName: ${{ parameters.artifactName }}
downloadPath: '$(artifactsDir)'
- task: DownloadBuildArtifacts@0
condition:
and(succeeded(),ne(variables['useBuildOutputFromBuildId'],''))
inputs:
buildType: specific
buildVersionToDownload: specific
project: $(System.TeamProjectId)
pipeline: ${{ parameters.useBuildOutputFromPipeline }}
buildId: $(useBuildOutputFromBuildId)
artifactName: ${{ parameters.artifactName }}
downloadPath: '$(artifactsDir)'
- task: CmdLine@1
displayName: 'Display Artifact Directory payload contents'
inputs:
filename: 'dir'
arguments: '/s $(artifactsDir)'
- task: powershell@2
displayName: 'PrepareHelixPayload.ps1'
inputs:
targetType: filePath
filePath: build\Helix\PrepareHelixPayload.ps1
arguments: -Platform '$(buildPlatform)' -Configuration '$(buildConfiguration)' -ArtifactName '${{ parameters.artifactName }}'
- task: CmdLine@1
displayName: 'Display Helix payload contents'
inputs:
filename: 'dir'
arguments: '/s $(Build.SourcesDirectory)\HelixPayload'
- template: helix-createprojfile-steps.yml
parameters:
condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite'))
testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\TerminalApp.LocalTests.dll'
outputProjFileName: 'RunTestsInHelix-TerminalAppLocalTests.proj'
testSuite: '${{ parameters.testSuite }}'
taefQuery: ${{ parameters.taefQuery }}
- template: helix-createprojfile-steps.yml
parameters:
condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite'))
testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\Conhost.UIA.Tests.dll'
outputProjFileName: 'RunTestsInHelix-HostTestsUIA.proj'
testSuite: '${{ parameters.testSuite }}'
taefQuery: ${{ parameters.taefQuery }}
- task: PublishBuildArtifacts@1
displayName: 'Publish generated .proj files'
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)
artifactName: ${{ parameters.artifactName }}
- task: DotNetCoreCLI@2
displayName: 'Run tests in Helix (open queues)'
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
command: custom
projects: build\Helix\RunTestsInHelix.proj
custom: msbuild
arguments: '$(helixCommonArgs) /p:IsExternal=true /p:Creator=Terminal /p:HelixTargetQueues=$(openHelixTargetQueues)'

View File

@@ -0,0 +1,15 @@
[CmdLetBinding()]
Param(
[Parameter(Mandatory=$true, Position=0)][string]$BuildPlatform,
[Parameter(Mandatory=$true, Position=1)][string]$RationalizedPlatform,
[Parameter(Mandatory=$true, Position=2)][string]$Configuration
)
$i = Get-Item .\packages\MuxCustomBuild*
$wtt = Join-Path -Path $i[0].FullName -ChildPath (Join-Path -Path 'tools' -ChildPath (Join-Path -Path $BuildPlatform -ChildPath 'wttlog.dll'))
$dest = Join-Path -Path .\bin -ChildPath (Join-Path -Path $RationalizedPlatform -ChildPath ($Configuration))
copy $wtt $dest
Exit 0

View File

@@ -2,12 +2,24 @@
Param(
[Parameter(Mandatory=$true, Position=0)][string]$MatchPattern,
[Parameter(Mandatory=$true, Position=1)][string]$Platform,
[Parameter(Mandatory=$true, Position=2)][string]$Configuration
[Parameter(Mandatory=$true, Position=2)][string]$Configuration,
[Parameter(Mandatory=$false, Position=3)][string]$LogPath
)
$testdlls = Get-ChildItem -Path ".\bin\$Platform\$Configuration" -Recurse -Filter $MatchPattern
&".\bin\$Platform\$Configuration\te.exe" $testdlls.FullName
$args = @();
if ($LogPath)
{
$args += '/enablewttlogging';
$args += '/appendwttlogging';
$args += "/logFile:$LogPath";
Write-Host "Wtt Logging Enabled";
}
&".\bin\$Platform\$Configuration\te.exe" $args $testdlls.FullName
if ($lastexitcode -Ne 0) { Exit $lastexitcode }

View File

@@ -106,6 +106,10 @@ Try {
Throw "Failed to find wt.exe/wtd.exe -- check the WAP packaging project"
}
If ($null -eq (Get-Item "$AppxPackageRootPath\OpenConsole.exe" -EA:Ignore)) {
Throw "Failed to find OpenConsole.exe -- check the WAP packaging project"
}
} Finally {
Remove-Item -Recurse -Force $AppxPackageRootPath
}

View File

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

Binary file not shown.

View File

@@ -1,9 +1,30 @@
### TAEF ###
### TAEF Overview ###
TAEF, the Test Authoring and Execution Framework, is used extensively within the Windows organization to test the operating system code in a unified manner for system, driver, and application code. As the console is a Windows OS Component, we strive to continue using the same system such that tests can be ran in a unified manner both externally to Microsoft as well as inside the official OS Build/Test system.
The [official documentation](https://msdn.microsoft.com/en-us/library/windows/hardware/hh439725\(v=vs.85\).aspx) for TAEF describes the basic architecture, usage, and functionality of the test system. It is similar to Visual Studio test, but a bit more comprehensive and flexible.
The [official documentation](https://docs.microsoft.com/en-us/windows-hardware/drivers/taef/) for TAEF describes the basic architecture, usage, and functionality of the test system. It is similar to Visual Studio test, but a bit more comprehensive and flexible.
For the purposes of the console project, you can run the tests using the *TE.exe* that matches the architecture for which the test was build (x86/x64) in the pattern
### Writing Tests
You may want to read the section [Authoring Tests in C++](https://docs.microsoft.com/en-us/windows-hardware/drivers/taef/authoring-tests-in-c--) before getting your hands dirty. Note that the quoted header name in `#include "WexTestClass.h"` might be a bit confusing. You are not required to copy TAEF headers into the project folder.
Use the [TAEF Verify Macros for C++](https://docs.microsoft.com/en-us/windows-hardware/drivers/taef/verify) in your test code to perform verifications.
### Running Tests
If you have Visual Studio and related C++ components installed, and you have successfully restored NuGets, you should have the TAEF test runner `te.exe` available locally as part of the `Taef.Redist.Wlk` package.
> Note that you cannot easily run TAEF tests directly through Visual Studio. The `Taef.Redist.Wlk` NuGet package comes with an adapter that will let you browse and execute TAEF tests inside of Visual Studio, but its performance and reliability prevent us from recommending it here.
In a "normal" CMD environment, `te.exe` may not be directly available. Try the following command to set up the development enviroment first:
```shell
.\tools\razzle.cmd
```
Then you should be able to use `%TAEF%` as an alias of the actual `te.exe`.
For the purposes of the OpenConsole project, you can run the tests using the `te.exe` that matches the architecture for which the test was built (x86/x64):
te.exe Console.Unit.Tests.dll
@@ -15,6 +36,15 @@ Limiting the tests to be run is also useful with:
Any pattern of class/method names can be specified after the */name:* flag with wildcard patterns.
For any further details on the functionality of the TAEF test runner, *TE.exe*, please see the documentation above or run the embedded help with
For any further details on the functionality of the TAEF test runner, please see the [Executing Tests](https://docs.microsoft.com/en-us/windows-hardware/drivers/taef/executing-tests) section in the official documentation. Or run the embedded help with
te.exe /!
If you use PowerShell, try the following command:
```powershell
Import-Module .\tools\OpenConsole.psm1
Invoke-OpenConsoleTests
```
`Invoke-OpenConsoleTests` supports a number of options, which you can enumerate by running `Invoke-OpenConsoleTests -?`.

View File

@@ -67,12 +67,12 @@ To update the version of a given package, use the following snippet
where:
- `$PackageName` is the name of the package, e.g. Microsoft.UI.Xaml
- `$OldVersionNumber` is the version number currently used, e.g. 2.5.0-prerelease.200609001
- `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.4.200117003-prerelease
- `$OldVersionNumber` is the version number currently used, e.g. 2.4.0-prerelease.200506002
- `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.5.0-prerelease.200812002
Example usage:
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.5.0-prerelease.200609001/2.4.200117003-prerelease/g'`
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.4.0-prerelease.200506002/2.5.0-prerelease.200812002/g'`
## Using .nupkg files instead of downloaded Nuget packages
If you want to use .nupkg files instead of the downloaded Nuget package, you can do this with the following steps:

View File

@@ -1,210 +1 @@
# Settings.json Documentation
## Globals
Properties listed below affect the entire window, regardless of the profile settings.
| Property | Necessity | Type | Default | Description |
| -------- | --------- | ---- | ------- | ----------- |
| `alwaysShowTabs` | _Required_ | Boolean | `true` | When set to `true`, tabs are always displayed. When set to `false` and `showTabsInTitlebar` is set to `false`, tabs only appear after typing <kbd>Ctrl</kbd> + <kbd>T</kbd>. |
| `copyOnSelect` | Optional | Boolean | `false` | When set to `true`, a selection is immediately copied to your clipboard upon creation. When set to `false`, the selection persists and awaits further action. |
| `copyFormatting` | Optional | Boolean | `false` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. |
| `largePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste. |
| `multiLinePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with a _new line_ character will display a warning asking you whether to continue or not with the paste. |
| `defaultProfile` | _Required_ | String | PowerShell guid | Sets the default profile. Opens by typing <kbd>Ctrl</kbd> + <kbd>T</kbd> or by clicking the '+' icon. The guid of the desired default profile is used as the value. |
| `initialCols` | _Required_ | Integer | `120` | The number of columns displayed in the window upon first load. |
| `initialPosition` | Optional | String | `","` | The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If `launchMode` is set to `"maximized"`, the window will be maximized on the monitor specified by those coordinates. |
| `initialRows` | _Required_ | Integer | `30` | The number of rows displayed in the window upon first load. |
| `launchMode` | Optional | String | `default` | Defines whether the Terminal will launch as maximized or not. Possible values: `"default"`, `"maximized"` |
| `theme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` |
| `showTerminalTitleInTitlebar` | _Required_ | Boolean | `true` | When set to `true`, titlebar displays the title of the selected tab. When set to `false`, titlebar displays "Windows Terminal". |
| `showTabsInTitlebar` | Optional | Boolean | `true` | When set to `true`, the tabs are moved into the titlebar and the titlebar disappears. When set to `false`, the titlebar sits above the tabs. |
| `snapToGridOnResize` | Optional | Boolean | `false` | When set to `true`, the window will snap to the nearest character boundary on resize. When `false`, the window will resize "smoothly" |
| `tabWidthMode` | Optional | String | `equal` | Sets the width of the tabs. Possible values: <br><ul><li>`"equal"`: sizes each tab to the same width</li><li>`"titleLength"`: sizes each tab to the length of its title</li><li>`"compact"`: sizes each tab to the length of its title when focused, and shrinks to the size of only the icon when the tab is unfocused.</li></ul> |
| `wordDelimiters` | Optional | String | <code>&nbsp;&#x2f;&#x5c;&#x28;&#x29;&#x22;&#x27;&#x2d;&#x3a;&#x2c;&#x2e;&#x3b;&#x3c;&#x3e;&#x7e;&#x21;&#x40;&#x23;&#x24;&#x25;&#x5e;&#x26;&#x2a;&#x7c;&#x2b;&#x3d;&#x5b;&#x5d;&#x7b;&#x7d;&#x7e;&#x3f;│</code><br>_(`│` is `U+2502 BOX DRAWINGS LIGHT VERTICAL`)_ | Determines the delimiters used in a double click selection. |
| `confirmCloseAllTabs` | Optional | Boolean | `true` | When set to `true` closing a window with multiple tabs open WILL require confirmation. When set to `false` closing a window with multiple tabs open WILL NOT require confirmation. |
| `startOnUserLogin` | Optional | Boolean | `false` | When set to `true` enables the launch of Windows Terminal at startup. Setting to `false` will disable the startup task entry. Note: if the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect. |
| `disabledProfileSources` | Optional | Array[String] | `[]` | Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup. This array can contain any combination of `Windows.Terminal.Wsl`, `Windows.Terminal.Azure`, or `Windows.Terminal.PowershellCore`. For more information, see [UsingJsonSettings.md](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md#dynamic-profiles) |
| `experimental.rendering.forceFullRepaint` | Optional | Boolean | `false` | When set to true, we will redraw the entire screen each frame. When set to false, we will render only the updates to the screen between frames. |
| `experimental.rendering.software` | Optional | Boolean | `false` | When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one. |
## Profiles
Properties listed below are specific to each unique profile.
| Property | Necessity | Type | Default | Description |
| -------- | --------- | ---- | ------- | ----------- |
| `guid` | _Required_ | String | | Unique identifier of the profile. Written in registry format: `"{00000000-0000-0000-0000-000000000000}"`. |
| `name` | _Required_ | String | | Name of the profile. Displays in the dropdown menu. <br>Additionally, this value will be used as the "title" to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. This "title" behavior can be overridden by using `tabTitle`. |
| `acrylicOpacity` | Optional | Number | `0.5` | When `useAcrylic` is set to `true`, it sets the transparency of the window for the profile. Accepts floating point values from 0-1. |
| `antialiasingMode` | Optional | String | `"grayscale"` | Controls how text is antialiased in the renderer. Possible values are "grayscale", "cleartype" and "aliased". Note that changing this setting will require starting a new terminal instance. |
| `background` | Optional | String | | Sets the background color of the profile. Overrides `background` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `backgroundImage` | Optional | String | | Sets the file location of the Image to draw over the window background. |
| `backgroundImageAlignment` | Optional | String | `center` | Sets how the background image aligns to the boundaries of the window. Possible values: `"center"`, `"left"`, `"top"`, `"right"`, `"bottom"`, `"topLeft"`, `"topRight"`, `"bottomLeft"`, `"bottomRight"` |
| `backgroundImageOpacity` | Optional | Number | `1.0` | Sets the transparency of the background image. Accepts floating point values from 0-1. |
| `backgroundImageStretchMode` | Optional | String | `uniformToFill` | Sets how the background image is resized to fill the window. Possible values: `"none"`, `"fill"`, `"uniform"`, `"uniformToFill"` |
| `closeOnExit` | Optional | String | `graceful` | Sets how the profile reacts to termination or failure to launch. Possible values: `"graceful"` (close when `exit` is typed or the process exits normally), `"always"` (always close) and `"never"` (never close). `true` and `false` are accepted as synonyms for `"graceful"` and `"never"` respectively. |
| `colorScheme` | Optional | String | `Campbell` | Name of the terminal color scheme to use. Color schemes are defined under `schemes`. |
| `commandline` | Optional | String | | Executable used in the profile. |
| `cursorColor` | Optional | String | | Sets the cursor color of the profile. Overrides `cursorColor` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `cursorHeight` | Optional | Integer | | Sets the percentage height of the cursor starting from the bottom. Only works when `cursorShape` is set to `"vintage"`. Accepts values from 25-100. |
| `cursorShape` | Optional | String | `bar` | Sets the cursor shape for the profile. Possible values: `"vintage"` ( &#x2583; ), `"bar"` ( &#x2503; ), `"underscore"` ( &#x2581; ), `"filledBox"` ( &#x2588; ), `"emptyBox"` ( &#x25AF; ) |
| `fontFace` | Optional | String | `Cascadia Mono` | Name of the font face used in the profile. We will try to fallback to Consolas if this can't be found or is invalid. |
| `fontSize` | Optional | Integer | `12` | Sets the font size. |
| `fontWeight` | Optional | String | `normal` | Sets the weight (lightness or heaviness of the strokes) for the given font. Possible values: `"thin"`, `"extra-light"`, `"light"`, `"semi-light"`, `"normal"`, `"medium"`, `"semi-bold"`, `"bold"`, `"extra-bold"`, `"black"`, `"extra-black"`, or the corresponding numeric representation of OpenType font weight. |
| `foreground` | Optional | String | | Sets the foreground color of the profile. Overrides `foreground` set in color scheme if `colorscheme` is set. Uses hex color format: `#rgb` or `"#rrggbb"`. |
| `hidden` | Optional | Boolean | `false` | If set to true, the profile will not appear in the list of profiles. This can be used to hide default profiles and dynamically generated profiles, while leaving them in your settings file. |
| `historySize` | Optional | Integer | `9001` | The number of lines above the ones displayed in the window you can scroll back to. |
| `icon` | Optional | String | | Image file location of the icon used in the profile. Displays within the tab and the dropdown menu. |
| `padding` | Optional | String | `8, 8, 8, 8` | Sets the padding around the text within the window. Can have three different formats: `"#"` sets the same padding for all sides, `"#, #"` sets the same padding for left-right and top-bottom, and `"#, #, #, #"` sets the padding individually for left, top, right, and bottom. |
| `scrollbarState` | Optional | String | `"visible"` | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` |
| `selectionBackground` | Optional | String | | Sets the selection background color of the profile. Overrides `selectionBackground` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `snapOnInput` | Optional | Boolean | `true` | When set to `true`, the window will scroll to the command input line when typing. When set to `false`, the window will not scroll when you start typing. |
| `altGrAliasing` | Optional | Boolean | `true` | By default Windows treats Ctrl+Alt as an alias for AltGr. When altGrAliasing is set to false, this behavior will be disabled. |
| `source` | Optional | String | | Stores the name of the profile generator that originated this profile. _There are no discoverable values for this field._ |
| `startingDirectory` | Optional | String | `%USERPROFILE%` | The directory the shell starts in when it is loaded. |
| `suppressApplicationTitle` | Optional | Boolean | `false` | When set to `true`, `tabTitle` overrides the default title of the tab and any title change messages from the application will be suppressed. When set to `false`, `tabTitle` behaves as normal. |
| `tabTitle` | Optional | String | | If set, will replace the `name` as the title to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. |
| `useAcrylic` | Optional | Boolean | `false` | When set to `true`, the window will have an acrylic background. When set to `false`, the window will have a plain, untextured background. The transparency only applies to focused windows due to OS limitation. |
| `experimental.retroTerminalEffect` | Optional | Boolean | `false` | When set to `true`, enable retro terminal effects. This is an experimental feature, and its continued existence is not guaranteed. |
## Schemes
Properties listed below are specific to each color scheme. [ColorTool](https://github.com/microsoft/terminal/tree/master/src/tools/ColorTool) is a great tool you can use to create and explore new color schemes. All colors use hex color format.
| Property | Necessity | Type | Description |
| -------- | ---- | ----------- | ----------- |
| `name` | _Required_ | String | Name of the color scheme. |
| `foreground` | _Required_ | String | Sets the foreground color of the color scheme. |
| `background` | _Required_ | String | Sets the background color of the color scheme. |
| `selectionBackground` | Optional | String | Sets the selection background color of the color scheme. |
| `cursorColor` | Optional | String | Sets the cursor color of the color scheme. |
| `black` | _Required_ | String | Sets the color used as ANSI black. |
| `blue` | _Required_ | String | Sets the color used as ANSI blue. |
| `brightBlack` | _Required_ | String | Sets the color used as ANSI bright black. |
| `brightBlue` | _Required_ | String | Sets the color used as ANSI bright blue. |
| `brightCyan` | _Required_ | String | Sets the color used as ANSI bright cyan. |
| `brightGreen` | _Required_ | String | Sets the color used as ANSI bright green. |
| `brightPurple` | _Required_ | String | Sets the color used as ANSI bright purple. |
| `brightRed` | _Required_ | String | Sets the color used as ANSI bright red. |
| `brightWhite` | _Required_ | String | Sets the color used as ANSI bright white. |
| `brightYellow` | _Required_ | String | Sets the color used as ANSI bright yellow. |
| `cyan` | _Required_ | String | Sets the color used as ANSI cyan. |
| `green` | _Required_ | String | Sets the color used as ANSI green. |
| `purple` | _Required_ | String | Sets the color used as ANSI purple. |
| `red` | _Required_ | String | Sets the color used as ANSI red. |
| `white` | _Required_ | String | Sets the color used as ANSI white. |
| `yellow` | _Required_ | String | Sets the color used as ANSI yellow. |
## Keybindings
Properties listed below are specific to each custom key binding.
| Property | Necessity | Type | Description |
| -------- | ---- | ----------- | ----------- |
| `command` | _Required_ | String | The command executed when the associated key bindings are pressed. |
| `keys` | _Required_ | Array[String] or String | Defines the key combinations used to call the command. |
| `action` | Optional | String | Adds additional functionality to certain commands. |
### Implemented Commands and Actions
Commands listed below are per the implementation in [`src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp`](https://github.com/microsoft/terminal/blob/master/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp).
Keybindings can be structured in the following manners:
For commands without arguments:
<br>
`{ "command": "commandName", "keys": [ "modifiers+key" ] }`
For commands with arguments:
<br>
`{ "command": { "action": "commandName", "argument": "value" }, "keys": ["modifiers+key"] }`
| Command | Command Description | Action (*=required) | Action Arguments | Argument Descriptions |
| ------- | ------------------- | ------ | ---------------- | ----------------- |
| `adjustFontSize` | Change the text size by a specified point amount. | `delta` | integer | Amount of size change per command invocation. |
| `closePane` | Close the active pane. | | | |
| `closeTab` | Close the current tab. | | | |
| `closeWindow` | Close the current window and all tabs within it. | | | |
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `singleLine` | boolean | When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text. |
| `duplicateTab` | Make a copy and open the current tab. | | | |
| `find` | Open the search dialog box. | | | |
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| `newTab` | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| `nextTab` | Open the tab to the right of the current one. | | | |
| `openNewTabDropdown` | Open the dropdown menu. | | | |
| `openSettings` | Open the settings file. | | | |
| `paste` | Insert the content that was copied onto the clipboard. | | | |
| `prevTab` | Open the tab to the left of the current one. | | | |
| `resetFontSize` | Reset the text size to the default value. | | | |
| `resizePane` | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| `scrollDown` | Move the screen down. | | | |
| `scrollUp` | Move the screen up. | | | |
| `scrollUpPage` | Move the screen up a whole page. | | | |
| `scrollDownPage` | Move the screen down a whole page. | | | |
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile`<br>7. `splitMode` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string<br>7. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name.<br>7. Controls how the pane splits. Only accepts `duplicate` which will duplicate the focused pane's profile into a new pane. |
| `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |
| `unbound` | Unbind the associated keys from any command. | | | |
### Accepted Modifiers and Keys
#### Modifiers
`ctrl+`, `shift+`, `alt+`
#### Keys
| Type | Keys |
| ---- | ---- |
| Function and Alphanumeric Keys | `f1-f24`, `a-z`, `0-9` |
| Symbols | ``` ` ```, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/` |
| Arrow Keys | `down`, `left`, `right`, `up`, `pagedown`, `pageup`, `pgdn`, `pgup`, `end`, `home`, `plus` |
| Action Keys | `tab`, `enter`, `esc`, `escape`, `space`, `backspace`, `delete`, `insert` |
| Numpad Keys | `numpad_0-numpad_9`, `numpad0-numpad9`, `numpad_add`, `numpad_plus`, `numpad_decimal`, `numpad_period`, `numpad_divide`, `numpad_minus`, `numpad_subtract`, `numpad_multiply` |
## Background Images and Icons
Some Terminal settings allow you to specify custom background images and icons. It is recommended that custom images and icons are stored in system-provided folders and are referred to using the correct [URI Schemes](https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes). URI Schemes provide a way to reference files independent of their physical paths (which may change in the future).
The most useful URI schemes to remember when customizing background images and icons are:
| URI Scheme | Corresponding Physical Path | Use / description |
| --- | --- | ---|
| `ms-appdata:///Local/` | `%localappdata%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\` | Per-machine files |
| `ms-appdata:///Roaming/` | `%localappdata%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\RoamingState\` | Common files |
> ⚠ Note: Do not rely on file references using the `ms-appx` URI Scheme (i.e. icons). These files are considered an internal implementation detail and may change name/location or may be omitted in the future.
### Icons
Terminal displays icons for each of your profiles which Terminal generates for any built-in shells - PowerShell Core, PowerShell, and any installed Linux/WSL distros. Each profile refers to a stock icon via the `ms-appx` URI Scheme.
> ⚠ Note: Do not rely on the files referenced by the `ms-appx` URI Scheme - they are considered an internal implementation detail and may change name/location or may be omitted in the future.
You can refer to you own icons if you wish, e.g.:
```json
"icon" : "C:\\Users\\richturn\\OneDrive\\WindowsTerminal\\icon-ubuntu-32.png",
```
> 👉 Tip: Icons should be sized to 32x32px in an appropriate raster image format (e.g. .PNG, .GIF, or .ICO) to avoid having to scale your icons during runtime (causing a noticeable delay and loss of quality.)
### Custom Background Images
You can apply a background image to each of your profiles, allowing you to configure/brand/style each of your profiles independently from one another if you wish.
To do so, specify your preferred `backgroundImage`, position it using `backgroundImageAlignment`, set its opacity with `backgroundImageOpacity`, and/or specify how your image fill the available space using `backgroundImageStretchMode`.
For example:
```json
"backgroundImage": "C:\\Users\\richturn\\OneDrive\\WindowsTerminal\\bg-ubuntu-256.png",
"backgroundImageAlignment": "bottomRight",
"backgroundImageOpacity": 0.1,
"backgroundImageStretchMode": "none"
```
> 👉 Tip: You can easily roam your collection of images and icons across all your machines by storing your icons and images in OneDrive (as shown above).
With these settings, your Terminal's Ubuntu profile would look similar to this:
![Custom icon and background image](../images/custom-icon-and-background-image.jpg)
⚠ This document has moved to [the Customize Settings section of the Windows Terminal documentation](https://docs.microsoft.com/windows/terminal/customize-settings/global-settings).

View File

@@ -4,9 +4,9 @@
"title": "Microsoft's Windows Terminal Settings Profile Schema",
"definitions": {
"KeyChordSegment": {
"pattern": "^(?<modifier>(ctrl|alt|shift)(?:\\+(ctrl|alt|shift)(?<!\\2))?(?:\\+(ctrl|alt|shift)(?<!\\2|\\3))?\\+)?(?<key>[^\\s+]|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": "^(?<modifier>(ctrl|alt|shift)(?:\\+(ctrl|alt|shift)(?<!\\2))?(?:\\+(ctrl|alt|shift)(?<!\\2|\\3))?\\+)?(?<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)$",
"type": "string",
"description": "The string should fit the format \"[ctrl+][alt+][shift+]<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.\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+]<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 (+)"
},
"Color": {
"default": "#",
@@ -26,46 +26,64 @@
],
"type": "string"
},
"BellStyle": {
"enum": [
"none",
"audible"
],
"type": "string"
},
"ProfileGuid": {
"default": "{}",
"pattern": "^\\{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\}$",
"type": "string"
},
"Icon": {
"description": "Image file location or an emoji to be used as an icon. Displays within the tab, the dropdown menu, and jumplist.",
"type": [
"string",
"null"
]
},
"ShortcutActionName": {
"enum": [
"adjustFontSize",
"closeOtherTabs",
"closePane",
"closeTab",
"closeTabsAfter",
"closeWindow",
"commandPalette",
"copy",
"duplicateTab",
"find",
"moveFocus",
"newTab",
"nextTab",
"openNewTabDropdown",
"openSettings",
"openTabColorPicker",
"paste",
"prevTab",
"renameTab",
"resetFontSize",
"resizePane",
"scrollDown",
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"sendInput",
"setColorScheme",
"setTabColor",
"splitPane",
"switchToTab",
"tabSearch",
"toggleAlwaysOnTop",
"toggleFocusMode",
"toggleFullscreen",
"toggleAlwaysOnTop",
"togglePaneZoom",
"toggleRetroEffect",
"find",
"setTabColor",
"openTabColorPicker",
"renameTab",
"commandPalette",
"wt",
"closeOtherTabs",
"closeTabsAfter",
"unbound"
],
"type": "string"
@@ -87,6 +105,40 @@
],
"type": "string"
},
"CopyFormat": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string",
"enum": [
"html",
"rtf"
]
}
},
{
"type": "string",
"enum": [
"html",
"rtf",
"all",
"none"
]
}
]
},
"AnchorKey": {
"enum": [
"ctrl",
"alt",
"shift"
],
"type": "string"
},
"NewTerminalArgs": {
"properties": {
"commandline": {
@@ -152,6 +204,18 @@
"type": "boolean",
"default": false,
"description": "If true, the copied content will be copied as a single line (even if there are hard line breaks present in the text). If false, newlines persist from the selected text."
},
"copyFormatting": {
"default": null,
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. Not setting this value inherits the behavior of the `copyFormatting` global setting.",
"oneOf": [
{
"$ref": "#/definitions/CopyFormat"
},
{
"type": "null"
}
]
}
}
}
@@ -220,6 +284,28 @@
],
"required": [ "direction" ]
},
"SendInputAction": {
"description": "Arguments corresponding to a Send Input Action",
"allOf": [
{
"$ref": "#/definitions/ShortcutAction"
},
{
"properties": {
"action": {
"type": "string",
"pattern": "sendInput"
},
"input": {
"type": "string",
"default": "",
"description": "The text input to feed into the shell. ANSI escape sequences may be used. Escape codes like \\x1b must be written as \\u001b."
}
}
}
],
"required": [ "input" ]
},
"SplitPaneAction": {
"description": "Arguments corresponding to a Split Pane Action",
"allOf": [
@@ -283,6 +369,23 @@
}
]
},
"SetColorSchemeAction": {
"description": "Arguments corresponding to a Set Color Scheme Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "setColorScheme" },
"colorScheme": {
"type": "string",
"default": "",
"description": "the name of the scheme to apply to the active pane"
}
}
}
],
"required": [ "colorScheme" ]
},
"WtAction": {
"description": "Arguments corresponding to a wt Action",
"allOf": [
@@ -308,14 +411,16 @@
"properties": {
"action": { "type": "string", "pattern": "closeOtherTabs" },
"index": {
"type": "integer",
"oneOf": [
{ "type": "integer" },
{ "type": null }
],
"default": "",
"description": "close the tabs following the tab at this index"
"description": "Close the tabs other than the one at this index. If no index is provided, use the focused tab's index."
}
}
}
],
"required": [ "index" ]
]
},
"CloseTabsAfterAction": {
"description": "Arguments for a closeTabsAfter action",
@@ -325,14 +430,16 @@
"properties": {
"action": { "type": "string", "pattern": "closeTabsAfter" },
"index": {
"type": "integer",
"oneOf": [
{ "type": "integer" },
{ "type": null }
],
"default": "",
"description": "close the tabs other than the one at this index"
"description": "Close the tabs following the tab at this index. If no index is provided, use the focused tab's index."
}
}
}
],
"required": [ "index" ]
]
},
"Keybinding": {
"additionalProperties": false,
@@ -347,9 +454,11 @@
{ "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SendInputAction" },
{ "$ref": "#/definitions/SplitPaneAction" },
{ "$ref": "#/definitions/OpenSettingsAction" },
{ "$ref": "#/definitions/SetTabColorAction" },
{ "$ref": "#/definitions/SetColorSchemeAction" },
{ "$ref": "#/definitions/WtAction" },
{ "$ref": "#/definitions/CloseOtherTabsAction" },
{ "$ref": "#/definitions/CloseTabsAfterAction" },
@@ -370,6 +479,14 @@
"type": "array"
}
]
},
"icon": { "$ref": "#/definitions/Icon" },
"name": {
"description": "The name that will appear in the command palette. If one isn't provided, the terminal will attempt to automatically generate a name.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -399,7 +516,12 @@
},
"copyFormatting": {
"default": true,
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard.",
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
"$ref": "#/definitions/CopyFormat"
},
"disableAnimations": {
"default": false,
"description": "When set to `true`, visual animations will be disabled across the application.",
"type": "boolean"
},
"largePasteWarning": {
@@ -433,28 +555,36 @@
},
"initialCols": {
"default": 120,
"description": "The number of columns displayed in the window upon first load.",
"description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",
"maximum": 999,
"minimum": 1,
"type": "integer"
},
"initialPosition": {
"$ref": "#/definitions/Coordinates",
"description": "The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If \"launchMode\" is set to maximized, the window will be maximized on the monitor specified by those coordinates."
"description": "The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), the window will be maximized on the monitor specified by those coordinates."
},
"initialRows": {
"default": 30,
"description": "The number of rows displayed in the window upon first load.",
"description": "The number of rows displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",
"maximum": 999,
"minimum": 1,
"type": "integer"
},
"startOnUserLogin": {
"default": false,
"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"
},
"launchMode": {
"default": "default",
"description": "Defines whether the Terminal will launch as maximized or not.",
"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.",
"enum": [
"fullscreen",
"maximized",
"default"
"default",
"focus",
"maximizedFocus"
],
"type": "string"
},
@@ -517,6 +647,11 @@
"default": true,
"description": "When set to \"true\" closing a window with multiple tabs open will require confirmation. When set to \"false\", the confirmation dialog will not appear.",
"type": "boolean"
},
"useTabSwitcher": {
"default": true,
"description": "When set to \"true\", the \"nextTab\" and \"prevTab\" commands will use the tab switcher UI.",
"type": "boolean"
}
},
"required": [
@@ -589,6 +724,11 @@
],
"type": "string"
},
"bellStyle": {
"default": "audible",
"description": "Controls what happens when the application emits a BEL character. When set to \"audible\", the Terminal will play a sound. When set to \"none\", nothing will happen.",
"$ref": "#/definitions/BellStyle"
},
"closeOnExit": {
"default": "graceful",
"description": "Sets how the profile reacts to termination or failure to launch. Possible values:\n -\"graceful\" (close when exit is typed or the process exits normally)\n -\"always\" (always close)\n -\"never\" (never close).\ntrue and false are accepted as synonyms for \"graceful\" and \"never\" respectively.",
@@ -704,10 +844,7 @@
"minimum": -1,
"type": "integer"
},
"icon": {
"description": "Image file location of the icon used in the profile. Displays within the tab and the dropdown menu.",
"type": ["string", "null"]
},
"icon":{ "$ref": "#/definitions/Icon" },
"name": {
"description": "Name of the profile. Displays in the dropdown menu.",
"minLength": 1,
@@ -716,8 +853,15 @@
"padding": {
"default": "8, 8, 8, 8",
"description": "Sets the padding around the text within the window. Can have three different formats:\n -\"#\" sets the same padding for all sides \n -\"#, #\" sets the same padding for left-right and top-bottom\n -\"#, #, #, #\" sets the padding individually for left, top, right, and bottom.",
"pattern": "^-?[0-9]+(\\.[0-9]+)?( *, *-?[0-9]+(\\.[0-9]+)?|( *, *-?[0-9]+(\\.[0-9]+)?){3})?$",
"type": "string"
"oneOf": [
{
"pattern": "^-?[0-9]+(\\.[0-9]+)?( *, *-?[0-9]+(\\.[0-9]+)?|( *, *-?[0-9]+(\\.[0-9]+)?){3})?$",
"type": "string"
},
{
"type": "integer"
}
]
},
"scrollbarState": {
"default": "visible",

View File

@@ -0,0 +1,54 @@
# Creating a New Project
## Creating a new WinRT Component DLL and referencing it in another project
When creating a new DLL, it was really helpful to reference an existing DLL's `.vcxproj` like `TerminalControl.vcxproj`. While you should mostly try to copy what the existing `.vcxproj` has, here's a handful of things to double check for as you go along.
- [ ] Make sure to `<Import>` our pre props at the _top_ of the vcxproj, and our post props at the _bottom_ of the vcxproj.
```
<!-- pre props -->
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<!-- everything else -->
<!-- post props -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
```
- [ ] Add a `<ProjectReference>` to your new `.vcxproj` in both `WindowsTerminal.vcxproj` and `TerminalApp.vcxproj`
- [ ] Add a `<Reference>` to `TerminalAppLib.vcxproj` similar to this:
```
<Reference Include="Microsoft.Terminal.NewDLL">
<HintPath>$(_BinRoot)TerminalNewDLL\Microsoft.Terminal.NewDLL.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
```
- [ ] Make sure the project has a `.def` file with the following lines. The `WINRT_GetActivationFactory` part is important to expose the new DLL's activation factory so that other projects can successfully call the DLL's `GetActivationFactory` to get the DLL's classes.
```
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
```
- For a bit more context on this whole process, the `AppXManifest.xml` file defines which classes belong to which DLLs. If your project wants class `X.Y.Z`, it can look it up in the manifest's definitions and see that it came from `X.Y.dll`. Then it'll load up the DLL, and call a particular function called `GetActivationFactory(L"X.Y.Z")` to get the class it wants. So, the definitions in `AppXManifest` are _required_ for this activation to work properly, and I found myself double checking the file to see that the definitions I expect are there.
- _Note_: If your new library eventually rolls up as a reference to our Centennial Packaging project `CascadiaPackage`, you don't have to worry about manually adding your definitions to the `AppXManifest.xml` because the Centennial Packaging project automatically enumerates the reference tree of WinMDs and stitches that information into the `AppXManifest.xml`. However, if your new project does _not_ ultimately roll up to a packaging project that will automatically put the references into `AppXManifest`, you will have to add them in manually.
### Troubleshooting
- If you hit an error that looks like this:
```
X found processing metadata file ..\blah1\Microsoft.UI.Xaml.winmd, type already exists in file ..\blah\NewDLLProject\Microsoft.UI.Xaml.winmd.
```
The `Microsoft.UI.Xaml.winmd` is showing up in the output folder when it shouldn't. Try adding this block at the top of your `.vcxproj`
```
<ItemDefinitionGroup>
<Reference>
<Private>false</Private>
</Reference>
</ItemDefinitionGroup>
```
This will make all references non-private, meaning "don't copy it into my folder" by default.
- If you hit a `Class not Registered` error, this might be because a class isn't getting registered in the app manifest. You can go check `src/cascadia/CascadiaPackage/bin/x64/Debug/AppX/AppXManifest.xml` to see if there exist entries to the classes of your newly created DLL. If the references aren't there, double check that you've added `<ProjectReference>` blocks to both `WindowsTerminal.vcxproj` and `TerminalApp.vcxproj`.
- If you hit an extremely vague error along the lines of `Error in the DLL`, and right before that line you notice that your new DLL is loaded and unloaded right after each other, double check that your new DLL's definitions show up in the `AppXManifest.xml` file. If your new DLL is included as a reference to a project that rolls up to `CascadiaPackage`, double check that you've created a `.def` file for the project. Otherwise if your new project _does not_ roll up to a package that populates the `AppXManifest` references for you, you'll have to add those references yourself.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

View File

@@ -0,0 +1,112 @@
---
author: Kayla Cinnamon @cinnamon-msft
created on: 2020-07-13
last updated: 2020-08-11
issue id: #1564
---
# Settings UI Design
## Abstract
This design document describes how each page of the settings UI will be laid out along with design mockups to display how the UI will appear. The mock ups are for appearance purposes and some layouts and naming may be different in this doc. This doc should be considered the final say.
## UI Design
### Overall navigation with Startup page
This is the list of the top-level navigation items that will appear on the left nav bar:
- General
- Startup
- Interaction
- Rendering
- Appearance
- Global
- Color schemes
- Themes*
- Profiles
- Defaults
- Enumerate profiles
- Add new
- Keyboard
- Mouse*
- Command Palette*
- Marketplace*
\* Themes, mouse, command palette, and marketplace will be added once they're implemented.
![Overall navigation](./navigation-2.png)
### Profile appearance page
This page requires special design because it includes the TerminalControl window to preview appearance changes. This preview window will appear on the following pages:
- Appearance - Color Schemes
- Profiles - Appearance
![Appearance page](./appearance.png)
### Keyboard page
The keyboard page will list the enabled key bindings and provide a way for users to add and remove them.
![Keyboard page](./keyboard.png)
When someone hovers over one of the items in the table, the Edit and Delete buttons will appear. Below is what the modal looks like if they were to click Edit on a command that does not have any arguments/actions. In the future, we would want this text box to be able to listen for key combinations. This would add a "listen" button to the UI.
![Keyboard page modal](./keyboard-modal.png)
If the command they select has additional arguments/actions, the modal will dynamically size as arguments/actions are added.
![Keyboard page modal add new arguments](./keyboard-modal-add.png)
![Keyboard page modal arguments](./keyboard-modal-args.png)
## Settings layout
Below is the list of all settings on their respective pages in the settings UI. The title row aligns with the navigation view on the left of the UI. Bolded headers in those columns align with top nav on the page.
| General - Startup | General - Interaction | General - Rendering | Appearance - Global | Appearance - Color Schemes | Profiles - Global | Profiles - Enumerate profiles | Profiles - Add new |
| ---------------- | --------------------- | ------------------- | ------------------- | -------------------------- | ----------------- | ----------------------------- | ------------------ |
| Default profile (dropdown) | Copy after selection is made (checkbox) | Software rendering (checkbox) | Theme (radio) | Name (text box) | **General** | **General** | **General** | **General** |
| Launch on startup (checkbox) | Copy formatting (checkbox) | Screen redrawing (checkbox) | Show/Hide the title bar (checkbox) | Cursor color (color picker) | Command line (text box) | Scrollbar visibility (radio) | Scrollbar visibility (radio) |
| Launch size (radio) | Word delimiters (text box) | | Show terminal title in title bar (checkbox) | Selection background (color picker) | Starting directory (browse button) | Command line (browse button) | Command line (browse button) |
| Launch position (text box) | Window resize behavior (checkbox) | | Always show tabs (checkbox) | Background (color picker) | Icon (browse button) | Starting directory (browse button) | Starting directory (browse button) |
| Columns on first launch (number picker) | | | Tab width mode (radio) | Foreground (color picker) | Tab title (text box) | Name (text box) | Name (text box) |
| Rows on first launch (number picker) | | | Hide close all tabs popup (checkbox) | Black (color picker) | Scrollbar visibility (radio) | Icon (browse button) | Icon (browse button) |
| Automatically create new profiles when new shells are installed (checkbox) | | | | Blue (color picker) | **Appearance** | Tab title (text box) | Tab title (text box) |
| | | | | Cyan (color picker) | Font face (text box) | **Appearance** | **Appearance** |
| | | | | Green (color picker) | Font size (number picker) | Retro terminal effects (checkbox) | Retro terminal effects (checkbox) |
| | | | | Purple (color picker) | Font weight (dropdown) | Font face (text box) | Font face (text box) |
| | | | | Red (color picker) | Padding (text box) | Font size (number picker) | Font size (number picker) |
| | | | | White (color picker) | Cursor shape (radio) | Font weight (dropdown) | Font weight (dropdown) |
| | | | | Yellow (color picker) | Cursor color (color picker) | Padding (text box) | Padding (text box) |
| | | | | Bright black (color picker) | Cursor height (number picker) | Cursor shape (radio) | Cursor shape (radio) |
| | | | | Bright blue (color picker) | Color scheme (dropdown) | Cursor color (color picker) | Cursor color (color picker) |
| | | | | Bright cyan (color picker) | Foreground color (color picker) | Cursor height (number picker) | Cursor height (number picker) |
| | | | | Bright green (color picker) | Background color (color picker) | Color scheme (dropdown) | Color scheme (dropdown) |
| | | | | Bright purple (color picker) | Selection background color (color picker) | Foreground color (color picker) | Foreground color (color picker) |
| | | | | Bright red (color picker) | Enable acrylic (checkbox) | Background color (color picker) | Background color (color picker) |
| | | | | Bright white (color picker) | Acrylic opacity (number picker) | Selection background color (color picker) | Selection background color (color picker) |
| | | | | Bright yellow (color picker) | Background image (browse button) | Enable acrylic (checkbox) | Enable acrylic (checkbox) |
| | | | | | Background image stretch mode (radio) | Acrylic opacity (number picker) | Acrylic opacity (number picker) |
| | | | | | Background image alignment (dropdown) | Background image (browse button) | Background image (browse button) |
| | | | | | Background image opacity (number picker) | Background image stretch mode (radio) | Background image stretch mode (radio) |
| | | | | | Retro terminal effects (checkbox) | Background image alignment (dropdown) | Background image alignment (dropdown) |
| | | | | | **Advanced** | Background image opacity (number picker) | Background image opacity (number picker) |
| | | | | | Hide profile from dropdown (checkbox) | **Advanced** | **Advanced** |
| | | | | | Suppress title changes (checkbox) | GUID (text box) | GUID (text box) |
| | | | | | Antialiasing text (radio) | Hide profile from dropdown (checkbox) | Hide profile from dropdown (checkbox) |
| | | | | | AltGr aliasing (checkbox) | Suppress title changes (checkbox) | Suppress title changes (checkbox) |
| | | | | | Scroll to input when typing (checkbox) | Antialiasing text (radio) | Antialiasing text (radio) |
| | | | | | History size (number picker) | AltGr aliasing (checkbox) | AltGr aliasing (checkbox) |
| | | | | | How the profile closes (radio) | Scroll to input when typing (checkbox) | Scroll to input when typing (checkbox) |
| | | | | | | History size (number picker) | History size (number picker) |
| | | | | | | How the profile closes (radio) | How the profile closes (radio) |
## Potential Issues
## Future considerations
## Resources

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1,122 @@
---
author: Kayla Cinnamon @cinnamon-msft
created on: 2020-06-29
last updated: 2020-08-10
issue id: #1564
---
# Settings UI Implementation
## Abstract
This spec describes the basic functionality of the settings UI, including disabling it, the navigation items, launch methods, and editing of settings. The specific layout of each page will defined in later design reviews.
## Inspiration
We have been wanting a settings UI since the dawn of Terminal time, so we need to define how it will interact with the application and how users should expect to interact with it.
## Solution Design
The settings UI will be the default experience. We will provide users an option to skip the settings UI and edit the raw JSON file.
### Ability to disable displaying the settings UI
Some users don't want a UI for the settings. We can update the `openSettings` key binding with a `settingsUI` option.
If people still like the UI but want to access the JSON file, we can provide an "Open the JSON file" button at the bottom of the navigation menu.
### Launch method: launch in a new tab
Clicking the settings button in the dropdown menu will open the settings UI in a new tab. This helps us take steps toward supporting non-terminal content in a tab. Users will be able to see their visual changes by using the preview window inside the settings UI on relevant pages.
#### We also considered: launch in a new window
Clicking the settings button in the dropdown menu will open the settings UI in a new window. This allows the user to edit their settings and see the Terminal live update with their changes.
In the Windows taskbar, the icon will appear as if Terminal has multiple windows open.
### Editing and saving settings: implement a save button
Users will only see their settings changes take place once they click "Save". Clicking "Save" will write to the settings.json file. This aligns with the functionality that exists today by editing the settings.json file in a text editor and saving it.
We will also be adding a TerminalControl inside the settings UI to preview what the changes will look like before actually saving them to the settings.json file.
#### We also considered: automatically save settings
As users edit fields in the settings UI, they are automatically saved and written to the JSON file. This allows the user to see their settings changes taking place in real time.
## UI/UX Design
Layout of all of the settings per page can be found in the [design doc](./design.md).
### Top-level navigation: more descriptive navigation
The navigation menu is broken up into more digestible sections. This aligns more closely to other terminals. The following are the proposed navigation items:
- General
- Startup
- Interaction
- Rendering
- Appearance
- Global
- Color schemes
- Themes*
- Profiles
- Defaults
- Enumerate profiles
- Add new
- Keyboard
- Mouse*
- Command Palette*
- Marketplace*
\* Themes, mouse, command palette, and marketplace will be added once they're implemented.
![Settings UI navigation 2](./navigation-2.png)
#### We also considered: align with JSON
The settings UI could have top-level navigation that aligns with the overall structure of the settings.json file. The following are the proposed navigation items:
- Globals
- Profiles
- Color schemes
- Bindings
For Bindings, it would have key bindings, mouse bindings, and command palette inside it.
![Settings UI navigation 1](./navigation.png)
## Capabilities
### Accessibility
This will have to undergo full accessibility testing because it is a new UI element. All items inside the settings UI should be accessible by a screen reader and the keyboard. Additionally, all of the settings UI will have to be localized.
### Security
This does not impact security.
### Reliability
This will not improve reliability.
### Compatibility
This will change the default experience to open the UI, rather than the JSON file in a text editor. This behavior can be reverted with the setting listed [above](#ability-to-disable-displaying-the-settings-ui).
### Performance, Power, and Efficiency
This does not affect performance, power, nor efficiency.
## Potential Issues
## Future considerations
- We will have to have design reviews for all of the content pages.
- The `hidden` property will need special consideration. Ideally, all profiles will appear in the settings regardless if `hidden` is set to `true`.
- We should have undo functionality. In a text editor, you can type `Ctrl+Z` however the settings UI is a bit more complex.
- Once we have a marketplace for themes and extensions, this should be added to the top-level navigation.
- As we add more features, the top-level navigation is subject to change in favor of improved usability.
## Resources

View File

@@ -0,0 +1,300 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-5-13
last updated: 2020-08-04
issue id: 1571
---
# New Tab Menu Customization
## Abstract
Many users have lots and _lots_ of profiles that they use. Some of these
profiles the user might not use that frequently. When that happens, the new tab
dropdown can become quite cluttered.
A common ask is for the ability to reorder and reorganize this dropdown. This
spec provides a design for how the user might be able to specify the
customization in their settings.
## Inspiration
Largely, this spec was inspired by discussion in
[#1571](https://github.com/microsoft/terminal/issues/1571#issuecomment-519504048)
and the _many_ linked threads.
## Solution Design
This design proposes adding a new setting `"newTabMenu"`. When unset, (the
default), the new tab menu is populated with all the profiles, in the order they
appear in the users settings file. When set, this enables the user to control
the appearance of the new tab dropdown. Let's take a look at an example:
```json
{
"profiles":{ ... },
"newTabMenu": [
{ "type":"profile", "profile": "cmd" },
{ "type":"profile", "profile": "Windows PowerShell" },
{ "type":"separator" },
{
"type":"folder",
"name": "ssh",
"icon": "C:\\path\\to\\icon.png",
"entries":[
{ "type":"profile", "profile": "Host 1" },
{ "type":"profile", "profile": "8.8.8.8" },
{ "type":"profile", "profile": "Host 2" }
]
},
{ "type":"separator" },
{ "type":"profile", "profile": "Ubuntu-18.04" },
{ "type":"profile", "profile": "Fedora" }
]
}
```
If a user were to use this as their new tab menu, that they would get is a menu
that looks like this:
![fig 1](Menu-Customization-000.png)
_fig 1_: A _very rough_ mockup of what this feature might look like
There are five `type`s of objects in this menu:
* `"type":"profile"`: This is a profile. Clicking on this entry will open a new
tab, with that profile. The profile is identified with the `"profile"`
parameter, which accepts either a profile `name` or GUID. The icon for this
entry will be the profile's icon, and the text on the entry will be the
profile's name.
* `"type":"separator"`: This represents a XAML `MenuFlyoutSeparator`, enabling
the user to visually space out entries.
* `"type":"folder"`: This represents a nested menu of entries.
- The `"name"` property provides a string of text to display for the group.
- The `"icon"` property provides a path to a image to use as the icon. This
property is optional.
- The `"entries"` property specifies a list of menu entries that will appear
nested under this entry. This can contain other `"type":"folder"` groups as
well!
* `"type":"action"`: This represents a menu entry that should execute a specific
`ShortcutAction`.
- the `id` property will specify the global action ID (see [#6899], [#7175])
to identify the action to perform when the user selects the entry. Actions
with invalid IDs will be ignored and omitted from the list.
- The text for this entry will be the action's label (which is
either provided as the `"name"` in the global list of actions, or the
generated name if no `name` was provided)
- The icon for this entry will similarly re-use the action's `icon`.
* `"type":"remainingProfiles"`: This is a special type of entry that will be
expanded to contain one `"type":"profile"` entry for every profile that was
not already listed in the menu. This will allow users to add one entry for
just "all the profiles they haven't manually added to the menu".
- This type of entry can only be specified once - trying to add it to the menu
twice will raise a warning, and ignore all but the first `remainingProfiles`
entry.
- This type of entry can also be set inside a `folder` entry, allowing users
to highlight only a couple profiles in the top-level of the menu, but
enabling all other profiles to also be accessible.
- The "name" of these entries will simply be the name of the profile
- The "icon" of these entries will simply be the profile's icon
The "default" new tab menu could be imagined as the following blob of json:
```json
{
"newTabMenu": [
{ "type":"remainingProfiles" }
]
}
```
### Other considerations
Also considered during the investigation for this feature was re-using the list
of profiles to expose the structure of the new tab menu. For example, doing
something like:
```json
"profiles": {
"defaults": {},
"list":
[
{ "name": "cmd" },
{ "name": "powershell" },
{ "type": "separator" },
{
"type": "folder" ,
"profiles": [
{ "name": "ubuntu" }
]
}
]
}
```
This option was not pursued because we felt that it needlessly complicated the
contents of the list of profiles objects. We'd rather have the `profiles` list
exclusively contain `Profile` objects, and have other elements of the json
_refer_ to those profiles. What if someone would like to have an action that
opened a new tab with profile index 4, and then they set that action as entry 4
in the profile's list? That would certainly be some sort of unexpected behavior.
Additionally, what if someone wants to have an entry that opens a tab with one
pane with one profile in it, and another pane with different profile in it? Or
what if they want the same profile to appear twice in the menu?
By overloading the structure of the `profiles` list, we're forcing all other
consumers of the list of profiles to care about the structure of the elements of
the list. These other consumers should only really care about the list of
profiles, and not necessarily how they're structured in the new tab dropdown.
Furthermore, it complicates the list of profiles, by adding actions intermixed
with the profiles.
The design chosen in this spec more cleanly separates the responsibilities of
the list of profiles and the contents of the new tab menu. This way, each object
can be defined independent of the structure of the other.
## UI/UX Design
See the above _figure 1_.
The profile's `icon` will also appear as the icon on `profile` entries. If
there's a keybinding bound to open a new tab with that profile, then that will
also be added to the `MenuFlyoutItem` as the accelerator text, similar to the
text we have nowadays.
Beneath the list of profiles will _always_ be the same "Settings", "Feedback"
and "About" entries, separated by a `MenuFlyoutSeparator`. This is consistent
with the UI as it exists with no customization. These entries cannot be removed
with this feature, only the list of profiles customized.
## Capabilities
### Accessibility
This menu will be added to the XAML tree in the same fashion as the current new
tab flyout, so there should be no dramatic change here.
### Security
_(no change expected)_
### Reliability
_(no change expected)_
### Compatibility
_(no change expected)_
### Performance, Power, and Efficiency
_(no change expected)_
## Potential Issues
Currently, the `openTab` and `splitPane` keybindings will accept a `index`
parameter to say either:
* "Create a new tab/pane with the N'th profile"
* "Create a new tab/pane with the profile at index N in the new
tab dropdown".
These two were previously synonymous, as the N'th profile was always the N'th in
the dropdown. However, with this change, we'll be changing the meaning of that
argument to mean explicitly the first option - "Open a tab/pane with the N'th
profile".
A previous version of this spec considered changing the meaning of that
parameter to mean "open the entry at index N", the second option. However, in
[Command Palette, Addendum 1], we found that naming that command would become
unnecessarily complex.
To cover that above scenario, we could consider adding an `index` parameter to
the `openNewTabDropdown` action. If specified, that would open either the N'th
action in the dropdown (ignoring separators), or open the dropdown with the n'th
item selected.
The N'th entry in the menu won't always be a profile: it might be a folder with
more options, or it might be an action (that might not be opening a new tab/pane
at all).
Given all the above scenarios, `openNewTabDropdown` with an `"index":N`
parameter will behave in the following ways. If the Nth top-level entry in the
new tab menu is a:
* `"type":"profile"`: perform the `newTab` or `splitPane` action with that profile.
* `"type":"folder"`: Focus the first element in the sub menu, so the user could
navigate it with the keyboard.
* `"type":"separator"`: Ignore these when counting top-level entries.
* `"type":"action"`: Perform the action.
So for example:
```
New Tab Button ▽
├─ Folder 1
│ └─ Profile A
│ └─ Action B
├─ Separator
├─ Folder 2
│ └─ Profile C
│ └─ Profile D
├─ Action E
└─ Profile F
```
And assuming the user has bound:
```json
{
"bindings":
[
{ "command": { "action": "openNewTabDropdown", "index": 0 }, "keys": "ctrl+shift+1" },
{ "command": { "action": "openNewTabDropdown", "index": 1 }, "keys": "ctrl+shift+2" },
{ "command": { "action": "openNewTabDropdown", "index": 2 }, "keys": "ctrl+shift+3" },
{ "command": { "action": "openNewTabDropdown", "index": 3 }, "keys": "ctrl+shift+4" },
]
}
```
* <kbd>ctrl+shift+1</kbd> focuses "Profile A", but the user needs to press
enter/space to creates a new tab/split
* <kbd>ctrl+shift+2</kbd> focuses "Profile C", but the user needs to press
enter/space to creates a new tab/split
* <kbd>ctrl+shift+3</kbd> performs Action E
* <kbd>ctrl+shift+4</kbd> Creates a new tab/split with Profile F
## Future considerations
* The user could set a `"name"`/`"text"`, or `"icon"` property to these menu
items manually, to override the value from the profile or action. These
settings would be totally optional, but it's not unreasonable that someone
might want this.
* We may want to consider adding a default icon for all folders or actions in
the menu. For example, a folder (like 📁) for `folder` entries, or something
like ⚡ for actions. We'll leave these unset by default, and evaluate setting
these icons by default in the future.
* Something considered during review was a way to specify "All my WSL profiles".
Maybe the user wants to have all their profiles generated by the WSL Distro
Generator appear in a "WSL" folder. This would likely require a more elaborate
filtering syntax, to be able to select only profiles where a certain property
has a specific value. Consider the user who has multiple "SSH
me@\<some host\>.com" profiles, and they want all their "SSH\*" profiles to
appear in an "SSH" folder. This feels out-of-scope for this spec.
* A similar structure could potentially also be used for customizing the context
menu within a control, or the context menu for the tab. (see [#3337])
- In both of those cases, it might be important to somehow refer to the
context of the current tab or control in the json. Think for example about
"Close tab" or "Close other tabs" - currently, those work by _knowing_ which
tab the "action" is specified for, not by actually using a `closeTab` action.
In the future, they might need to be implemented as something like
- Close Tab: `{ "action": "closeTab", "index": "${selectedTab.index}" }`
- Close Other Tabs: `{ "action": "closeTabs", "otherThan": "${selectedTab.index}" }`
- Close Tabs to the Right: `{ "action": "closeTabs", "after": "${selectedTab.index}" }`
<!-- Footnotes -->
[#2046]: https://github.com/microsoft/terminal/issues/2046
[Command Palette, Addendum 1]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md
[#3337]: https://github.com/microsoft/terminal/issues/3337
[#6899]: https://github.com/microsoft/terminal/issues/6899
[#7175]: https://github.com/microsoft/terminal/issues/7175

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,228 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-07-13
last updated: 2020-07-22
issue id: 6899
---
# Action IDs
## Abstract
This document is intended to serve as an addition to the [Command Palette Spec],
as well as the [New Tab Menu Customization Spec].
As we come to rely more on actions being a mechanism by which the user defines
"do something in the Terminal", we'll want to make it even easier for users to
re-use the actions that they've already defined, as to reduce duplicated json as
much as possible. This spec proposes a mechanism by which actions could be
uniquely identifiable, so that the user could refer to bindings in other
contexts without needing to replicate an entire json blob.
## Solution Design
This spec was largely inspired by the following diagram from @DHowett:
![figure 1](data-mockup.png)
The goal is to introduce an `id` parameter by which actions could be uniquely
refered to. If we'd ever like to use an action outside the list of `actions`, we
can simply refer to the action's ID, allowing the user to only define the action
_once_.
We'll start by renaming `bindings` to `actions`. `bindings` was suggested as a
rename for `keybindings` in [#6532], as a way to make the name more generic.
Discussion with the team lead to the understanding that the name `actions` would
be even better, as a way of making the meaning of the "list of actions" more
obvious.
When we're parsing `actions`, we'll make three passes:
* The first pass will scan the list for objects with an `id` property. We'll
attempt to parse those entries into `ActionAndArgs` which we'll store in the
global `id->ActionAndArgs` map. If any entry doesn't have an `id` set, we'll
skip it in this phase. If an entry doesn't have a `command` set, we'll ignore
it in this pass.
* The second pass will scan for _keybindings_. Any entries with `keys` set will
create a `KeyChord->ActionAndArgs` entry in the keybindings map. If the entry
has an `id` set, then we'll simply re-use the action we've already parsed for
the `id`, from the action map. If there isn't an `id`, then we'll parse the
action manually at this time. Entries without a `keys` set will be ignored in
this pass.
* The final pass will be to generate _commands_. Similar to the keybindings
pass, we'll attempt to lookup actions for entries with an `id` set. If there
isn't an `id`, then we'll parse the action manually at this time. We'll then
get the name for the entry, either from the `name` property if it's set, or
the action's `GenerateName` method.
For a visual representation, let's assume the user has the following in their
`actions`:
![figure 2](data-mockup-actions.png)
We'll first parse the `actions` to generate the mapping of `id`->`Actions`:
![figure 3](data-mockup-actions-and-ids.png)
Then, we'll parse the `actions` to generate the mapping of keys to actions, with
some actions already being defined in the map of `id`->`Actions`:
![figure 4](data-mockup-actions-and-ids-and-keys.png)
When layering `actions`, if a later settings file contains an action with the
same `id`, it will replace the current value. In this way, users can redefine
actions, or remove default ones (with something like `{ "id":
"Terminal.OpenTab", "command":null }`
We'd maintain a large list of default actions, each with unique `id`s set. These
are all given `id`'s with a `Terminal.` prefix, to easily identify them as
built-in, default actions. Not all of these actions will be given keys, but they
will all be given `id`s.
> 👉 NOTE: The IDs for the default actions will need to be manually created, not
> autogenerated. These `id`s are not strings displayed in the user interface, so
> localization is not a concern.
As we add additional menus to the Terminal, like the customization for the new
tab dropdown, or the tab context menu, or the `TermControl` context menu, they
could all refer to these actions by `id`, rather than duplicating the same json.
### Existing Scenarios
Keybindings will still be stored as a `keys->Action` mapping, so the user will
still be able to override default keybindings exactly the same as before.
Similarly, commands in the Command Palette will continue using their existing
`name->Action` mapping they're currently using. For a binding like
```json
{ "keys": "ctrl+alt+x", "id": "Terminal.OpenDefaultSettings" },
```
* We'll bind whatever action is defined as `Terminal.OpenDefaultSettings` to
<kbd>ctrl+alt+x</kbd>.
* We'll use whatever action is defined as `Terminal.OpenDefaultSettings` to
generate a name for the command palette.
### Future Context Menus
In [New Tab Menu Customization Spec], we discuss allowing the user to bind
actions to the new tab menu. In that spec, they can do so with something like
the following:
```json
{
"newTabMenu": [
{ "type":"action", "command": { "action": "adjustFontSize", "delta": 1 }, }
{ "type":"action", "command": { "action": "adjustFontSize", "delta": -1 }, }
{ "type":"action", "command": "resetFontSize", }
{ "type":"profile", "profile": "cmd" },
{ "type":"profile", "profile": "Windows PowerShell" },
{ "type":"separator" },
{
"type":"folder",
"name": "Settings...",
"icon": "C:\\path\\to\\icon.png",
"entries":[
{ "type":"action", "command": "openSettings" },
{ "type":"action", "command": { "action": "openSettings", "target": "defaultsFile" } },
]
}
]
}
```
In this example, the user has also exposed the "Increase font size", "Decrease
font size", and "Reset font size" actions, as well as the settings files in a
submenu. With this proposal, the above could instead be re-written as:
```json
{
"newTabMenu": [
{ "type":"action", "id": "Terminal.IncreaseFontSize" },
{ "type":"action", "id": "Terminal.DecreaseFontSize" },
{ "type":"action", "id": "Terminal.ResetFontSize" },
{ "type":"profile", "profile": "cmd" },
{ "type":"profile", "profile": "Windows PowerShell" },
{ "type":"separator" },
{
"type":"folder",
"name": "Settings...",
"icon": "C:\\path\\to\\icon.png",
"entries":[
{ "type":"action", "id": "Terminal.OpenDefaultSettings" },
{ "type":"action", "id": "Terminal.OpenSettings" },
]
}
]
}
```
In this example, the actions are looked up from the global map using the `id`
provided, enabling the user to re-use their existing definitions. If the user
re-defined the `Terminal.IncreaseFontSize` action to mean something else, then
the action in the new tab menu will also be automatically updated.
Furthermore, when additional menus are added (such as the tab context menu, or
the `TermControl` context menu), these could also leverage a similar syntax to
the above to allow re-use of the `id` parameter.
Discussion with the team also suggested that users shouldn't be able to define
actions in these menus _at all_. The actions should exclusively be defined in
`actions`, and other menus should only be able to refer to these actions by
`id`.
## UI/UX Design
There's not a whole lot of UI for this feature specifically. This is largely
behind-the-scenes refactoring of how actions can be defined.
## Capabilities
### Accessibility
_(not applicable)_
### Security
_(no change expected)_
### Reliability
_(no change expected)_
### Compatibility
_(no change expected)_
### Performance, Power, and Efficiency
_(no change expected)_
## Potential Issues
This won't necessarily play well with iterable commands in the Command Palette,
but that's okay. For iterable commands, users will still need to define the
actions manually.
## Future considerations
* See the following issues for other places where this might be useful:
- [#1912] - Context Menu for Tabs
* See also [#5524], [#5025], [#5633]
- [#3337] - Right-click menu inside TerminalControl (w/ Copy & Paste?)
* See also [#5633] and [#5025], both those actions seem reasonable in either
the tab context menu or the control context menu.
<!-- Footnotes -->
[Command Palette Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Command%20Palette.md
[New Tab Menu Customization Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md
[#1571]: https://github.com/microsoft/terminal/issues/1571
[#1912]: https://github.com/microsoft/terminal/issues/1912
[#3337]: https://github.com/microsoft/terminal/issues/3337
[#5025]: https://github.com/microsoft/terminal/issues/5025
[#5524]: https://github.com/microsoft/terminal/issues/5524
[#5633]: https://github.com/microsoft/terminal/issues/5633
[#6532]: https://github.com/microsoft/terminal/issues/6532
[#6899]: https://github.com/microsoft/terminal/issues/6899

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1,251 @@
---
author: Carlos Zamora @carlos-zamora
created on: 2020-07-10
last updated: 2020-07-10
issue id: [#885](https://github.com/microsoft/terminal/issues/885)
---
# Terminal Settings Model
## Abstract
This spec proposes a major refactor and repurposing of the TerminalSettings project as the TerminalSettingsModel.
TerminalSettingsModel would be responsible for exposing, serializing, and deserializing settings as WinRT objects
for Windows Terminal. In doing so, Terminal's settings model is accessible as WinRT objects to existing components
like TerminalApp, TerminalControl, and TerminalCore. Additionally, Terminal Settings can be used by the Settings UI or
Shell Extensions to modify or reference Terminal's settings respectively.
## Inspiration
The main driver for this change is the Settings UI. The Settings UI will need to read and modify Terminal's settings
objects. At the time of writing this spec, the Terminal's settings are serialized as objects in the TerminalApp project.
To access these objects via XAML, the Settings UI needs them to be WinRT objects. Additional features that need the
settings objects to be WinRT objects include future shell extensions, like jumplist.
## Solution Design
### Terminal Settings Model: Objects and Projections
The following TerminalApp objects will become WinRT objects and will be moved to the TerminalSettingsModel project
(formerly TerminalSettings):
- ColorScheme
- Profile
- GlobalAppSettings
- CascadiaSettings
The TerminalSettingsModel project will have a root namespace of `Microsoft.Terminal.Settings.Model`.
Adjacent to the introduction of these settings objects, `IControlSettings` and `ICoreSettings` will be moved
to the `Microsoft.Terminal.TerminalControl` namespace. This allows for a better consumption of the
settings model that is covered later in the (Consumption section)[#terminal-settings-model:-consumption].
#### Moving/Splitting the Action Model
Windows Terminal represents actions via several objects:
- `AppKeyBindings`: a map of all the defined keybindings and their corresponding actions
- `ActionAndArgs`: a (de)serializable action (this holds more objects inside of it, but we won't focus on that for now)
- `ShortcutActionDispatch`: responsible for dispatching events pertinent to a given ActionAndArgs object
`TerminalApp`'s `TerminalPage` handles any events dispatched by the `ShortcutActionDispatch`.
With the introduction of the TerminalSettingsModel, we will split `AppKeyBindings` using a `KeyMapping` class.
This separation will look something like the following:
```c++
namespace TerminalApp
{
[default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.TerminalControl.IKeyBindings
{
AppKeyBindings();
// NOTE: It may be possible to move both of these to the constructor instead
void SetDispatch(ShortcutActionDispatch dispatch);
void SetKeyMap(KeyMapping keymap);
}
}
namespace TerminalSettingsModel
{
[default_interface] runtimeclass KeyMapping
{
void SetKeyBinding(ActionAndArgs actionAndArgs, Microsoft.Terminal.TerminalControl.KeyChord chord);
void ClearKeyBinding(Microsoft.Terminal.TerminalControl.KeyChord chord);
Microsoft.Terminal.TerminalControl.KeyChord GetKeyBindingForAction(ShortcutAction action);
Microsoft.Terminal.TerminalControl.KeyChord GetKeyBindingForActionWithArgs(ActionAndArgs actionAndArgs);
}
}
```
This separation leaves `AppKeyBindings` with the responsibility of detecting and dispatching actions, whereas
`KeyMapping` handles the (de)serialization and navigation of the key bindings.
### Terminal Settings Model: Serialization and Deserialization
Introducing these `Microsoft.Terminal.Settings.Model` WinRT objects also allow the serialization and deserialization
logic from TerminalApp to be moved to TerminalSettings. `JsonUtils` introduces several quick and easy methods
for setting serialization. This will be moved into the `Microsoft.Terminal.Settings.Model` namespace too.
Deserialization will be an extension of the existing `JsonUtils` `ConversionTrait` struct template. `ConversionTrait`
already includes `FromJson` and `CanConvert`. Serialization would be handled by a `ToJson` function.
### Terminal Settings Model: Warnings and Serialization Errors
Today, if the deserialization of `CascadiaSettings` encounters any errors, an exception is thrown and caught/handled
by falling back to a simple `CascadiaSettings` object. However, WinRT does not support exceptions.
To get around this issue, when `CascadiaSettings` encounters a serialization error, it must internally record
any pertinent information for that error, and return the simple `CascadiaSettings` as if nothing happened.
The consumer must then call `CascadiaSettings::GetErrors()` and `CascadiaSettings::GetWarnings()` to properly
understand whether an error ocurred and how to present that to the user.
#### TerminalApp: Loading and Reloading Changes
TerminalApp will construct and reference a `CascadiaSettings settings` as follows:
- TerminalApp will have a global reference to the "settings.json" filepath
- construct an `CascadiaSettings` using `CascadiaSettings("settings.json")`. This builds an `CascadiaSettings`
from the "defaults.json" file data (which is already compiled as a string literal)
and layers the settings.json data on top of it.
- check for errors/warnings, and handle them appropriately
This will be different from the current model which has the settings.json path hardcoded, and is simplified
to a `LoadAll()` call wrapped in error handlers.
**NOTE:** This model allows us to layer even more settings files on top of the existing Terminal Settings
Model, if so desired. This could be helpful when importing additional settings files from an external location
such as a marketplace.
When TerminalApp detects a change to settings.json, it'll repeat the steps above. We could cache the result from
constructing an `CascadiaSettings` from "defaults.json" data to improve performance.
#### TerminalControl: Acquiring and Applying the Settings
At the time of writing this spec, TerminalApp constructs `TerminalControl.TerminalSettings` WinRT objects
to expose `IControlSettings` and `ICoreSettings` to any hosted terminals. In moving `IControlSettings`
and `ICoreSettings` down to the TerminalControl layer, TerminalApp can now have better control over
how to expose relevant settings to a TerminalControl instance.
`TerminalSettings` (which implements `IControlSettings` and `ICoreSettings`) will be moved to
TerminalApp and act as a bridge connecting `CascadiaSettings` to the TermControl. It will operate
very similarly as it does today. On construction of the TermControl or hot-reload,
`TerminalSettings` will be constructed by copying the relevant values of `CascadiaSettings`.
Then, it will be passed to TermControl (and TermCore by extension).
## UI/UX Design
N/A
## Capabilities
### Accessibility
N/A
### Security
N/A
### Reliability
N/A
### Compatibility
N/A
### Performance, Power, and Efficiency
## Potential Issues
N/A
## Future considerations
### TerminalSettings: passing by reference
`TermApp` synthesizes a `TerminalSettings` by copying the relevant values of `CascadiaSettings`,
then giving it to a Terminal Control. Some visual keybindings and interactions like ctrl+scroll
and ctrl+shift+scroll to change the font size and acrylic opacity operate by directly modifying
the value of the instantiated `TerminalSettings`. However, when a settings reload occurs,
these instanced changes are lost.
`TerminalSettings` can be used as a WinRT object that references (instead of copies) the relevant
values of `CascadiaSettings`. This would prevent those instanced changes from being lost on a settings
reload.
Since previewing commands like `setColorScheme` would require a clone of the existing `TerminalSettings`,
a `Clone` API can be added on `TerminalSettings` to accomplish that. When passing by value,
`TerminalSettings` can just overwrite the existing property (i.e.: color scheme). When passing
by reference, a slightly more complex mechanism is required to override the value.
Now, instead of overwriting the value, we need to override the reference to a constant value
(i.e.: `snapOnInput=true`) or a referenced value (i.e.: `colorScheme`).
### Layering Additional Settings
As we begin to introduce more sources that affect the settings (via extensions or themes),
we can introduce a `LayerSettings(String path)`. This layers the new settings file
onto the existing `CascadiaSettings`. This is already done internally, we would just expose
it via C++/WinRT.
```c++
runtimeclass CascadiaSettings
{
// Load a settings file, and layer those changes on top of the existing CascadiaSettings
void LayerSettings(String path);
}
```
### Settings UI: Modifying and Applying the Settings (DRAFT)
```c++
runtimeclass CascadiaSettings
{
// Create a copy of the existing CascadiaSettings
CascadiaSettings Clone();
// Compares object to "source" and applies changes to
// the settings file at "outPath"
void Save(String outPath);
}
```
The Settings UI will also have a reference to the `CascadiaSettings settings` from TerminalApp
as `settingsSource`. When the Settings UI is opened up, the Settings UI will also have its own `CascadiaSettings settingsClone`
that is a clone of TerminalApp's `CascadiaSettings`.
```c++
settingsClone = settingsSource.Clone()
```
As the user navigates the Settings UI, the relevant contents of `settingsClone` will be retrieved and presented.
As the user makes changes to the Settings UI, XAML will update `settingsClone` using XAML data binding.
When the user saves/applies the changes in the XAML, `settingsClone.Save("settings.json")` is called;
this compares the changes between `settingsClone` and `settingsSource`, then injects the changes (if any) to `settings.json`.
As mentioned earlier, TerminalApp detects a change to "settings.json" to update its `CascadiaSettings`.
Since the above triggers a change to `settings.json`, TerminalApp will also update itself. When
something like this occurs, `settingsSource` will automatically be updated too.
In the case that a user is simultaneously updating the settings file directly and the Settings UI,
`settingsSource` and `settingsClone` can be compared to ensure that the Settings UI, the TerminalApp,
and the settings files are all in sync.
**NOTE:** In the event that the user would want to export their current configuration, `Save`
can be used to export the changes to a new file.
### Reserialization (DRAFT)
After deserializing the settings, injecting the new json into settings.json
should not remove the existing comments or formatting.
The reserialization process takes place right after comparing the `settingsSource` and `settingsClone` objects.
For each setting found in the diff, we go to the relevant part of the JSON and see if the key is already there.
If it is, we update the value to be the one from `settingsClone`. Otherwise, we append the key/value pair
at the end of the section (much like we do with dynamic profiles in `profiles`).
## Resources
- [Preview Commands](https://github.com/microsoft/terminal/issues/6689)
- [New JSON Utils](https://github.com/microsoft/terminal/pull/6590)
- [Spec: Settings UI](https://github.com/microsoft/terminal/pull/6720)

View File

@@ -22,15 +22,14 @@ Below is the schedule for when milestones will be included in release builds of
| Milestone End Date | Milestone Name | Preview Release Blog Post |
| ------------------ | -------------- | ------------------------- |
| 2020-06-18 | [1.1] in Windows Terminal Preview | [Windows Terminal Preview 1.1 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-1-release/) |
| 2020-07-31 | [1.2] in Windows Terminal Preview<br>[1.1] in Windows Terminal | |
| 2020-08-31 | 1.3 in Windows Terminal Preview<br>[1.2] in Windows Terminal | |
| 2020-09-30 | 1.4 in Windows Terminal Preview<br>1.3 in Windows Terminal | |
| 2020-10-31 | 1.5 in Windows Terminal Preview<br>1.4 in Windows Terminal | |
| 2020-11-30 | 1.6 in Windows Terminal Preview<br>1.5 in Windows Terminal | |
| 2020-12-31 | 1.7 in Windows Terminal Preview<br>1.6 in Windows Terminal | |
| 2021-01-31 | 1.8 in Windows Terminal Preview<br>1.7 in Windows Terminal | |
| 2021-02-28 | 1.9 in Windows Terminal Preview<br>1.8 in Windows Terminal | |
| 2021-03-31 | 1.10 in Windows Terminal Preview<br>1.9 in Windows Terminal | |
| 2020-07-31 | [1.2] in Windows Terminal Preview<br>[1.1] in Windows Terminal | [Windows Terminal Preview 1.2 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-2-release/) |
| 2020-08-31 | [1.3] in Windows Terminal Preview<br>[1.2] in Windows Terminal | [Windows Terminal Preview 1.3 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-3-release/) |
| 2020-09-30 | [1.4] in Windows Terminal Preview<br>[1.3] in Windows Terminal | [Windows Terminal Preview 1.4 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-4-release/) |
| 2020-11-30 | [1.5] in Windows Terminal Preview<br>[1.4] in Windows Terminal | |
| 2020-12-31 | 1.6 in Windows Terminal Preview<br>[1.5] in Windows Terminal | |
| 2021-01-31 | 1.7 in Windows Terminal Preview<br>1.6 in Windows Terminal | |
| 2021-02-28 | 1.8 in Windows Terminal Preview<br>1.8 in Windows Terminal | |
| 2021-03-31 | 1.9 in Windows Terminal Preview<br>1.9 in Windows Terminal | |
| 2021-04-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
| 2021-05-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
@@ -50,11 +49,11 @@ The following are a list of the key scenarios we're aiming to deliver for Termin
| Priority\* | Scenario | Description/Notes |
| ---------- | -------- | ----------------- |
| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.<br><br>Issue: [#1564] |
| 0 | Command palette | A popup menu to list possible actions and commands.<br><br>Issues: [#5400], [#2046]<br>Spec: [#2193] |
| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.<br><br>Issue: [#1564]<br>Specs: [#6720], [#6904]<br>Implementation: [#7283], [#7370] |
| 0 | Command palette | A popup menu to list possible actions and commands.<br><br>Issues: [#5400], [#2046]<br>Spec: [#2193]<br>Implementation: [#6635] |
| 1 | Tab tear-off | The ability to tear a tab out of the current window and spawn a new window or attach it to a separate window.<br><br>Issue: [#1256]<br>Spec: [#2080] |
| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.<br><br>Issue: [#574] |
| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal<br><br>Issue: [#492]<br>Spec: [#2080] |
| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.<br><br>Issue: [#574]<br>Implementation: [#7251] |
| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal<br><br>Issue: [#492]<br>Spec: [#2080], [#7414] |
| 1 | Overall theme support | Tab coloring, title bar coloring, pane border coloring, pane border width, definition of what makes a theme<br><br>Issue: [#3327]<br>Spec: [#5772] |
| 1 | Open tab as admin/other user | Open tab in existing Windows Terminal instance as admin (if Terminal was run unelevated) or as another user.<br><br>Issue: [#5000] |
| 1 | Traditional opacity | Have a transparent background without the acrylic blur.<br><br>Issue: [#603] |
@@ -62,7 +61,7 @@ The following are a list of the key scenarios we're aiming to deliver for Termin
| 2 | Infinite scrollback | Have an infinite history for the text buffer.<br><br>Issue: [#1410] |
| 2 | Pane management | All issues listed out in the original issue. Some features include pane resizing with mouse, pane zooming, and opening a pane by prompting which profile to use.<br><br>Issue: [#1000] |
| 2 | Theme marketplace | Marketplace for creation and distribution of themes.<br>Dependent on overall theming |
| 2 | Jump list | Show profiles from task bar (on right click)/start menu.<br><br>Issue: [#576] |
| 2 | Jump list | Show profiles from task bar (on right click)/start menu.<br><br>Issue: [#576]<br>Implementation: [#7515] |
| 2 | Open with multiple tabs | A setting that allows Windows Terminal to launch with a specific tab configuration (not using only command line arguments).<br><br>Issue: [#756] |
| 3 | Open in Windows Terminal | Functionality to right click on a file or folder and select Open in Windows Terminal.<br><br>Issue: [#1060]<br>Implementation: [#6100] |
| 3 | Session restoration | Launch Windows Terminal and the previous session is restored with the proper tab and pane configuration and starting directories.<br><br>Issues: [#961], [#960], [#766] |
@@ -80,16 +79,26 @@ Feature Notes:
[1.1]: https://github.com/microsoft/terminal/milestone/24
[1.2]: https://github.com/microsoft/terminal/milestone/25
[1.3]: https://github.com/microsoft/terminal/milestone/26
[1.4]: https://github.com/microsoft/terminal/milestone/28
[1.5]: https://github.com/microsoft/terminal/milestone/30
[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
[#6904]: https://github.com/microsoft/terminal/pull/6904
[#7283]: https://github.com/microsoft/terminal/pull/7283
[#7370]: https://github.com/microsoft/terminal/pull/7370
[#5400]: https://github.com/microsoft/terminal/issues/5400
[#2046]: https://github.com/microsoft/terminal/issues/2046
[#2193]: https://github.com/microsoft/terminal/pull/2193
[#6635]: https://github.com/microsoft/terminal/pull/6635
[#1256]: https://github.com/microsoft/terminal/issues/1256
[#2080]: https://github.com/microsoft/terminal/pull/2080
[#574]: https://github.com/microsoft/terminal/issues/574
[#7251]: https://github.com/microsoft/terminal/pull/7251
[#492]: https://github.com/microsoft/terminal/issues/492
[#2080]: https://github.com/microsoft/terminal/pull/2080
[#7414]: https://github.com/microsoft/terminal/pull/7414
[#3327]: https://github.com/microsoft/terminal/issues/3327
[#5772]: https://github.com/microsoft/terminal/pull/5772
[#5000]: https://github.com/microsoft/terminal/issues/5000
@@ -100,6 +109,7 @@ Feature Notes:
[#1410]: https://github.com/microsoft/terminal/issues/1410
[#1000]: https://github.com/microsoft/terminal/issues/1000
[#576]: https://github.com/microsoft/terminal/issues/576
[#7515]: https://github.com/microsoft/terminal/pull/7515
[#756]: https://github.com/microsoft/terminal/issues/756
[#1060]: https://github.com/microsoft/terminal/issues/1060
[#6100]: https://github.com/microsoft/terminal/pull/6100

View File

@@ -1,13 +1,7 @@
# Adding profiles for third-party tools
This doc will hopefully provide a useful guide for adding profiles for common
third-party tools to your
[settings.json](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md)
file.
All of these profiles are provided _without_ their `guid` set. If you'd like to
set any of these profiles as your _default_ profile, you'll need to make sure to
[generate a unique guid](https://www.guidgenerator.com/) for them manually.
This doc will hopefully provide a useful guide for adding profiles for common third-party tools to your
[settings.json](https://docs.microsoft.com/en-us/windows/terminal/customize-settings/profile-settings) file.
## Anaconda
@@ -15,10 +9,10 @@ Assuming that you've installed Anaconda into `%USERPROFILE%\Anaconda3`:
```json
{
"commandline" : "cmd.exe /k \"%USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3\"",
"icon" : "%USERPROFILE%/Anaconda3/Menu/anaconda-navigator.ico",
"name" : "Anaconda3",
"startingDirectory" : "%USERPROFILE%"
"commandline": "cmd.exe /k \"%USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3\"",
"icon": "%USERPROFILE%\\Anaconda3\\Menu\\anaconda-navigator.ico",
"name": "Anaconda3",
"startingDirectory": "%USERPROFILE%"
}
```
@@ -28,23 +22,23 @@ Assuming that you've installed cmder into `%CMDER_ROOT%`:
```json
{
"commandline" : "cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"",
"name" : "cmder",
"icon" : "%CMDER_ROOT%/icons/cmder.ico",
"startingDirectory" : "%USERPROFILE%"
"commandline": "cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"",
"name": "cmder",
"icon": "%CMDER_ROOT%\\icons\\cmder.ico",
"startingDirectory": "%USERPROFILE%"
}
```
## Cygwin
Assuming that you've installed Cygwin into `C:/Cygwin`:
Assuming that you've installed Cygwin into `C:\Cygwin`:
```json
{
"name" : "Cygwin",
"commandline" : "C:/Cygwin/bin/bash --login -i",
"icon" : "C:/Cygwin/Cygwin.ico",
"startingDirectory" : "C:/Cygwin/bin"
"name": "Cygwin",
"commandline": "C:\\Cygwin\\bin\\bash --login -i",
"icon": "C:\\Cygwin\\Cygwin.ico",
"startingDirectory": "C:\\Cygwin\\bin"
}
```
@@ -58,49 +52,49 @@ Assuming that you've installed Far into `c:\Program Files\Far Manager`:
```json
{
"name" : "Far",
"commandline" : "\"c:\\program files\\far manager\\far.exe\"",
"startingDirectory" : "%USERPROFILE%",
"useAcrylic" : false
"name": "Far",
"commandline": "\"c:\\program files\\far manager\\far.exe\"",
"startingDirectory": "%USERPROFILE%",
"useAcrylic": false
},
```
## Git Bash
Assuming that you've installed Git Bash into `C:/Program Files/Git`:
Assuming that you've installed Git Bash into `C:\\Program Files\\Git`:
```json
{
"name" : "Git Bash",
"commandline" : "C:/Program Files/Git/bin/bash.exe -li",
"icon" : "C:/Program Files/Git/mingw64/share/git/git-for-windows.ico",
"startingDirectory" : "%USERPROFILE%"
"name": "Git Bash",
"commandline": "C:\\Program Files\\Git\\bin\\bash.exe -li",
"icon": "C:\\Program Files\\Git\\mingw64\\share\\git\\git-for-windows.ico",
"startingDirectory": "%USERPROFILE%"
}
````
## Git Bash (WOW64)
Assuming that you've installed Git Bash into `C:/Program Files (x86)/Git`:
Assuming that you've installed Git Bash into `C:\\Program Files (x86)\\Git`:
```json
{
"name" : "Git Bash",
"commandline" : "%ProgramFiles(x86)%/Git/bin/bash.exe -li",
"icon" : "%ProgramFiles(x86)%/Git/mingw32/share/git/git-for-windows.ico",
"startingDirectory" : "%USERPROFILE%"
"name": "Git Bash",
"commandline": "%ProgramFiles(x86)%\\Git\\bin\\bash.exe -li",
"icon": "%ProgramFiles(x86)%\\Git\\mingw32\\share\\git\\git-for-windows.ico",
"startingDirectory": "%USERPROFILE%"
}
```
## MSYS2
Assuming that you've installed MSYS2 into `C:/msys64`:
Assuming that you've installed MSYS2 into `C:\\msys64`:
```json
{
"name" : "MSYS2",
"commandline" : "C:/msys64/msys2_shell.cmd -defterm -no-start -mingw64",
"icon": "C:/msys64/msys2.ico",
"startingDirectory" : "C:/msys64/home/user"
"name": "MSYS2",
"commandline": "C:\\msys64\\msys2_shell.cmd -defterm -no-start -mingw64",
"icon": "C:\\msys64\\msys2.ico",
"startingDirectory": "C:\\msys64\\home\\user"
}
````
@@ -110,9 +104,9 @@ Assuming that you've installed VS 2019 Professional:
```json
{
"name" : "Developer Command Prompt for VS 2019",
"commandline" : "cmd.exe /k \"C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/Tools/VsDevCmd.bat\"",
"startingDirectory" : "%USERPROFILE%"
"name": "Developer Command Prompt for VS 2019",
"commandline": "cmd.exe /k \"C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/Tools/VsDevCmd.bat\"",
"startingDirectory": "%USERPROFILE%"
}
```

View File

@@ -1,201 +1 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-01-16
last updated: 2020-01-17
---
# Using the `wt.exe` Commandline
As of [#4023], the Windows Terminal now supports accepting arguments on the
commandline, to enable launching the Terminal in a non-default configuration.
This document serves as a reference for all the parameters you can currently
pass, and gives some examples of how to use the `wt` commandline.
> NOTE: If you're running the Terminal built straight from the repo, you'll need
> to use `wtd.exe` and `wtd` instead of `wt.exe` and `wt`.
1. [Commandline Reference](#Reference)
1. [Commandline Examples](#Examples)
## Reference
### Options
#### `--help,-h,-?,/?,`
Display the help message.
## Subcommands
#### `new-tab`
`new-tab [terminal_parameters]`
Opens a new tab with the given customizations. On its _first_ invocation, also
opens a new window. Subsequent `new-tab` commands will all open new tabs in the
same window. <sup>[[1](#footnote-1)]</sup>
**Parameters**:
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `split-pane`
`split-pane [-H,--horizontal|-V,--vertical] [terminal_parameters]`
Creates a new pane in the currently focused tab by splitting the given pane
vertically or horizontally. <sup>[[1](#footnote-1)]</sup>
**Parameters**:
* `-H,--horizontal`, `-V,--vertical`: Used to indicate which direction to split
the pane. `-V` is "vertically" (think `[|]`), and `-H` is "horizontally"
(think `[-]`). If omitted, defaults to "auto", which splits the current pane
in whatever the larger dimension is. If both `-H` and `-V` are provided,
defaults to vertical.
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `focus-tab`
`focus-tab [--target,-t tab-index]|[--next,-n]|[--previous,-p]`
Moves focus to a given tab.
**Parameters**:
* `--target,-t tab-index`: moves focus to the tab at index `tab-index`. If
omitted, defaults to `0` (the first tab). Will display an error if combined
with either of `--next` or `--previous`.
* `-n,--next`: Move focus to the next tab. Will display an error if combined
with either of `--previous` or `--target`.
* `-p,--previous`: Move focus to the previous tab. Will display an error if
combined with either of `--next` or `--target`.
#### `[terminal_parameters]`
Some of the preceding commands are used to create a new terminal instance.
These commands are listed above as accepting `[terminal_parameters]` as a
parameter. For these commands, `[terminal_parameters]` can be any of the
following:
`[--profile,-p profile-name] [--startingDirectory,-d starting-directory] [commandline]`
* `--profile,-p profile-name`: Use the given profile to open the new tab/pane,
where `profile-name` is the `name` or `guid` of a profile. If `profile-name`
does not match _any_ profiles, uses the default.
* `--startingDirectory,-d starting-directory`: Overrides the value of
`startingDirectory` of the specified profile, to start in `starting-directory`
instead.
* `commandline`: A commandline to replace the default commandline of the
selected profile. If the user wants to use a `;` in this commandline, it
should be escaped as `\;`.
### Notes
* <span id="footnote-1"></span> [1]: If you try to run a `wt` commandline while running in a Windows Terminal window, the commandline will _always_ create a new window by default. Being able to run `wt` commandlines in the _current_ window is planned in the future - for more information, refer to [#4472].
## Examples
### Open Windows Terminal in the current directory
```powershell
wt -d .
```
This will launch a new Windows Terminal window in the current working directory.
It will use your default profile, but instead of using the `startingDirectory`
property from that it will use the current path. This is especially useful for
launching the Windows Terminal in a directory you currently have open in an
`explorer.exe` window.
### Opening with multiple panes
If you want to open with multiple panes in the same tab all at once, you can use
the `split-pane` command to create new panes.
Consider the following commandline:
```powershell
wt ; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe
```
This creates a new Windows Terminal window with one tab, and 3 panes:
* `wt`: Creates the new tab with the default profile
* `split-pane -p "Windows PowerShell"`: This will create a new pane, split from
the parent with the default profile. This pane will open with the "Windows
PowerShell" profile
* `split-pane -H wsl.exe`: This will create a third pane, split _horizontally_
from the "Windows PowerShell" pane. It will be running the default profile,
and will use `wsl.exe` as the commandline (instead of the default profile's
`commandline`).
### Using multiple commands from PowerShell
The Windows Terminal uses the semicolon character `;` as a delimiter for
separating subcommands in the `wt` commandline. Unfortunately, `powershell` also
uses `;` as a command separator. To work around this you can use the following
tricks to help run multiple wt sub commands from powershell. In all the
following examples, we'll be creating a new Terminal window with three panes -
one running `cmd`, one with `powershell`, and a last one running `wsl`.
In each of the following examples, we're using the `Start-Process` command to
run `wt`. For more information on why we're using `Start-Process`, see ["Using
`start`"](#using-start) below.
#### Single quoted parameters (if you aren't calculating anything):
In this example, we'll wrap all the parameters to `wt` in single quotes (`'`)
```PowerShell
start wt 'new-tab "cmd"; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe'
```
#### Escaped quotes (if you need variables):
If you'd like to pass a value contained in a variable to the `wt` commandline,
instead use the following syntax:
```PowerShell
$ThirdPane = "wsl.exe"
start wt "new-tab cmd; split-pane -p `"Windows PowerShell`" ; split-pane -H $ThirdPane"
```
Note the usage of `` ` `` to escape the double-quotes (`"`) around "Windows
Powershell" in the `-p` parameter to the `split-pane` sub-command.
#### Using `start`
All the above examples explicitly used `start` to launch the Terminal.
In the following examples, we're going to not use `start` to run the
commandline. Instead, we'll try two other methods of escaping the commandline:
* Only escaping the semicolons so that `powershell` will ignore them and pass
them straight to `wt`.
* Using `--%`, so powershell will treat the rest of the commandline as arguments
to the application.
```PowerShell
wt new-tab "cmd" `; split-pane -p "Windows PowerShell" `; split-pane -H wsl.exe
```
```Powershell
wt --% new-tab cmd ; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe
```
In both these examples, the newly created Windows Terminal window will create
the window by correctly parsing all the provided commandline arguments.
However, these methods are _not_ recommended currently, as Powershell will wait
for the newly-created Terminal window to be closed before returning control to
Powershell. By default, Powershell will always wait for Windows Store
applications (like the Windows Terminal) to close before returning to the
prompt. Note that this is different than the behavior of `cmd`, which will return
to the prompt immediately. See
[Powershell/PowerShell#9970](https://github.com/PowerShell/PowerShell/issues/9970)
for more details on this bug.
[#4023]: https://github.com/microsoft/terminal/pull/4023
[#4472]: https://github.com/microsoft/terminal/issues/4472
⚠ This document has moved to [Using command-line arguments for Windows Terminal](https://docs.microsoft.com/windows/terminal/command-line-arguments).

View File

@@ -1,483 +1 @@
# Editing Windows Terminal JSON Settings
One way (currently the only way) to configure Windows Terminal is by editing the
`settings.json` settings file. At the time of writing you can open the settings
file in your default editor by selecting `Settings` from the WT pull down menu.
The settings are stored in the file `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json`.
As of [#2515](https://github.com/microsoft/terminal/pull/2515), the settings are
split into _two_ files: a hardcoded `defaults.json`, and `settings.json`, which
contains the user settings. Users should only be concerned with the contents of
the `settings.json`, which contains their customizations. The `defaults.json`
file is only provided as a reference of what the default settings are. For more
details on how these two files work, see [Settings
Layering](#settings-layering). To view the default settings file, click on the
"Settings" button while holding the <kbd>Alt</kbd> key.
Details of specific settings can be found [here](../cascadia/SettingsSchema.md).
A general introduction is provided below.
The settings are grouped under four headings:
1. Global: Settings that apply to the whole application e.g. Default profile, initial size etc.
2. Key Bindings: Actually a sub field of the global settings, but worth discussing separately
3. Profiles: A group of settings to be applied to a tab when it is opened using that profile. E.g. shell to use, cursor shape etc.
4. Schemes: Sets of colors for background, text etc. that can be used by profiles
## Global Settings
These settings define startup defaults, and application-wide settings that might
not affect a particular terminal instance.
* Theme
* Title Bar options
* Initial size
* Default profile used when the Windows Terminal is started
Example settings include
```json
{
"defaultProfile" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
"initialCols" : 120,
"initialRows" : 50,
"theme" : "system",
"keybindings" : []
...
}
```
These global properties should exist in the root json object.
## Key Bindings
This is an array of key chords and shortcuts to invoke various commands.
Each command can have more than one key binding.
> 👉 **Note**: Key bindings is a subfield of the global settings and
> key bindings apply to all profiles in the same manner.
For example, here's a sample of the default keybindings:
```json
{
"keybindings":
[
{ "command": "closePane", "keys": ["ctrl+shift+w"] },
{ "command": "copy", "keys": ["ctrl+shift+c"] },
{ "command": "newTab", "keys": ["ctrl+shift+t"] },
// etc.
]
}
```
You can also use a single key chord string as the value of `"keys"`.
It will be treated as a chord of length one.
This will allow you to simplify the above snippet as follows:
```json
{
"keybindings":
[
{ "command": "closePane", "keys": "ctrl+shift+w" },
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "newTab", "keys": "ctrl+shift+t" },
// etc.
]
}
```
A list of default key bindings is available [here](https://github.com/microsoft/terminal/blob/master/src/cascadia/TerminalApp/defaults.json#L204).
### Unbinding keys
If you ever come across a key binding that you're unhappy with, it's possible to
easily change the keybindings. For example, vim uses <kbd>Ctrl+^</kbd> as a
binding for "switch to previous buffer", which conflicts with the Terminal's
default keybinding for "open a new tab with the sixth profile". If you'd like to
unbind that keybinding, and allow the keystroke to fall through to vim, you can
add the following to your keybindings:
```json
{
"command" : null, "keys" : ["ctrl+shift+6"]
},
```
This will _unbind_ <kbd>Ctrl+Shift+6</kbd>, allowing vim to use the keystroke
instead of the terminal.
### Binding multiple keys
You can have multiple key chords bound to the same action. To do this, simply
add multiple bindings for the same action. For example:
```json
"keybindings" :
[
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "copy", "keys": "ctrl+c" },
{ "command": "copy", "keys": "enter" }
]
```
In this snippet, all three of <kbd>ctrl+shift+c</kbd>, <kbd>ctrl+c</kbd> and <kbd>enter</kbd> are bound to `copy`.
## Profiles
A profile contains the settings applied when a new WT tab is opened. Each
profile is identified by a GUID and contains a number of other fields.
> 👉 **Note**: The `guid` property is the unique identifier for a profile. If
> multiple profiles all have the same `guid` value, you may see unexpected
> behavior.
* Which command to execute on startup - this can include arguments.
* Starting directory
* Which color scheme to use (see Schemes below)
* Font face and size
* Various settings to control appearance. E.g. Opacity, icon, cursor appearance, display name etc.
* Other behavioral settings. E.g. Close on exit, snap on input, .....
Example settings include
```json
"closeOnExit" : true,
"colorScheme" : "Campbell",
"commandline" : "wsl.exe -d Debian",
"cursorColor" : "#FFFFFF",
"cursorShape" : "bar",
"fontFace" : "Hack",
"fontSize" : 9,
"guid" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
"name" : "Debian",
"startingDirectory" : "%USERPROFILE%\\wslhome"
....
```
> 👉 **Note**: To use backslashes in any path field, you'll need to escape them following JSON escaping rules (like shown above). As an alternative, you can use forward slashes ("%USERPROFILE%/wslhome").
The profile GUID is used to reference the default profile in the global settings.
The values for background image stretch mode are documented [here](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.media.stretch).
### Hiding a profile
If you want to remove a profile from the list of profiles in the new tab
dropdown, but keep the profile around in your `settings.json` file, you can add
the property `"hidden": true` to the profile's json. This can also be used to
remove the default `cmd` and PowerShell profiles, if the user does not wish to
see them.
## Color Schemes
Each scheme defines the color values to be used for various terminal escape sequences.
Each schema is identified by the name field. Examples include
```json
"name" : "Campbell",
"background" : "#0C0C0C",
"black" : "#0C0C0C",
"blue" : "#0037DA",
"foreground" : "#F2F2F2",
"green" : "#13A10E",
"red" : "#C50F1F",
"white" : "#CCCCCC",
"yellow" : "#C19C00"
...
```
The schema name can then be referenced in one or more profiles.
## Settings layering
The runtime settings are actually constructed from _three_ sources:
* The default settings, which are hardcoded into the application, and available
in `defaults.json`. This includes the default keybindings, color schemes, and
profiles for both Windows PowerShell and Command Prompt (`cmd.exe`).
* Dynamic Profiles, which are generated at runtime. These include Powershell
Core, the Azure Cloud Shell connector, and profiles for and WSL distros.
* The user settings from `settings.json`.
Settings from each of these sources are "layered" upon the settings from
previous sources. In this manner, the user settings in `settings.json` can
contain _only the changes from the default settings_. For example, if a user
would like to only change the color scheme of the default `cmd` profile to
"Solarized Dark", you could change your cmd profile to the following:
```js
{
// Make changes here to the cmd.exe profile
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"colorScheme": "Solarized Dark"
}
```
Here, we know we're changing the `cmd` profile, because the `guid`
`"{0caa0dad-35be-5f56-a8ff-afceeeaa6101}"` is `cmd`'s unique GUID. Any profiles
with that GUID will all be treated as the same object. Any changes in that
profile will overwrite those from the defaults.
Similarly, you can overwrite settings from a color scheme by defining a color
scheme in `settings.json` with the same name as a default color scheme.
If you'd like to unbind a keystroke that's bound to an action in the default
keybindings, you can set the `"command"` to `"unbound"` or `null`. This will
allow the keystroke to fallthrough to the commandline application instead of
performing the default action.
### Dynamic Profiles
When dynamic profiles are created at runtime, they'll be added to the
`settings.json` file. You can identify these profiles by the presence of a
`"source"` property. These profiles are tied to their source - if you uninstall
a linux distro, then the profile will remain in your `settings.json` file, but
the profile will be hidden.
The Windows Terminal uses the `guid` property of these dynamically-generated
profiles to uniquely identify them. If you try to change the `guid` of a
dynamically-generated profile, the Terminal will automatically recreate a new
entry for that profile.
If you'd like to disable a particular dynamic profile source, you can add that
`source` to the global `"disabledProfileSources"` array. For example, if you'd
like to hide all the WSL profiles, you could add the following setting:
```json
"disabledProfileSources": ["Windows.Terminal.WSL"],
...
```
> 👉 **NOTE**: On launch, if a dynamic profile generator is enabled, it will
> always add new profiles it detects to your list of profiles. If you delete a
> dynamically generated profile from your list of profiles, it will just get
> re-added the next time the Terminal is launched! To remove a dynamic profile
> from your list of profiles, make sure to set `"hidden": true` in the profile.
### Default settings
In [#2325](https://github.com/microsoft/terminal/issues/2325), we introduced the
concept of "Default Profile Settings". These are settings that will apply to all
of your profiles by default. Profiles can still override these settings
individually. With default profile settings, you can easily make changes to all
your profiles at once. For example, given the following settings:
```json
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"profiles":
[
{
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe",
"fontFace": "Cascadia Code",
"fontSize": 14
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"commandline": "cmd.exe",
"fontFace": "Cascadia Code",
"fontSize": 14
},
{
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
"name" : "cmder",
"startingDirectory" : "%USERPROFILE%",
"fontFace": "Cascadia Code",
"fontSize": 14
}
],
```
All three of these profiles are using "Cascadia Code" as their `"fontFace"`, and
14 as their `fontSize`. With default profile settings, you can easily set these
properties for all your profiles, like so:
```json
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"profiles": {
"defaults":
{
"fontFace": "Cascadia Code",
"fontSize": 14
},
"list": [
{
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe"
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"commandline": "cmd.exe"
},
{
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
"name" : "cmder",
"startingDirectory" : "%USERPROFILE%"
}
]
},
```
Note that the `profiles` property has changed in this example from a _list_ of
profiles, to an _object_ with two properties:
* a `list` that contains the list of all the profiles
* the new `defaults` object, which contains all the settings that should apply to
every profile.
What if I wanted a profile to have a different value for a property other than
the default? Simply set the property in the profile's entry to override the
value from `defaults`. Let's say you want the `cmd` profile to have _"Consolas"_
as the font, but the rest of your profiles to still have _"Cascadia Code"_. You
could achieve that with the following:
```json
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"profiles": {
"defaults":
{
"fontFace": "Cascadia Code",
"fontSize": 14
},
"list": [
{
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe"
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"commandline": "cmd.exe",
"fontFace": "Consolas"
},
{
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
"name" : "cmder",
"startingDirectory" : "%USERPROFILE%"
}
]
},
```
In the above settings, the `"fontFace"` in the `cmd.exe` profile overrides the
`"fontFace"` from the `defaults`.
## Configuration Examples
### Add a custom background to the WSL Debian terminal profile
1. Download the [Debian JPG logo](https://www.debian.org/logos/openlogo-100.jpg)
2. Put the image in the
`$env:LocalAppData\Packages\Microsoft.WindowsTerminal_<randomString>\LocalState\`
directory (same directory as your `settings.json` file).
__NOTE__: You can put the image anywhere you like, the above suggestion happens to be convenient.
3. Open your WT json properties file.
4. Under the Debian Linux profile, add the following fields:
```json
"backgroundImage": "ms-appdata:///Local/openlogo-100.jpg",
"backgroundImageOpacity": 1,
"backgroundImageStretchMode" : "none",
"backgroundImageAlignment" : "topRight",
```
5. Make sure that `useAcrylic` is `false`.
6. Save the file.
7. Jump over to WT and verify your changes.
Notes:
1. You will need to experiment with different color settings
and schemes to make your terminal text visible on top of your image
2. If you store the image in the UWP directory (the same directory as your settings.json file),
then you should use the URI style path name given in the above example.
More information about UWP URI schemes [here](https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes).
3. Instead of using a UWP URI you can use a:
1. URL such as
`http://open.esa.int/files/2017/03/Mayer_and_Bond_craters_seen_by_SMART-1-350x346.jpg`
2. Local file location such as `C:\Users\Public\Pictures\openlogo.jpg`
### Adding Copy and Paste Keybindings
As of [#1093](https://github.com/microsoft/terminal/pull/1093) (first available
in Windows Terminal v0.3), the Windows Terminal now supports copy and paste
keyboard shortcuts. However, if you installed and ran the terminal before that,
you won't automatically get the new keybindings added to your settings. If you'd
like to add shortcuts for copy and paste, you can do so by inserting the
following objects into your `globals.keybindings` array:
```json
{ "command": "copy", "keys": ["ctrl+shift+c"] },
{ "command": "paste", "keys": ["ctrl+shift+v"] }
```
> 👉 **Note**: you can also add a keybinding for the `copy` command with the argument `"trimWhitespace": true`. This removes newlines as the text is copied to your clipboard.
This will add copy and paste on <kbd>ctrl+shift+c</kbd>
and <kbd>ctrl+shift+v</kbd> respectively.
You can set the keybindings to whatever you'd like. If you prefer
<kbd>ctrl+c</kbd> to copy, then set the `keys` to `"ctrl+c"`.
You can even set multiple keybindings for a single action if you'd like. For example:
```json
{
"command" : "paste",
"keys" :
[
"ctrl+shift+v"
]
},
{
"command" : "paste",
"keys" :
[
"shift+insert"
]
}
```
will bind both <kbd>ctrl+shift+v</kbd> and
<kbd>shift+Insert</kbd> to `paste`.
> 👉 **Note**: If you set your copy keybinding to `"ctrl+c"`, you'll only be able to send
an interrupt to the commandline application using <kbd>Ctrl+C</kbd> when there's
no text selection. Additionally, if you set `paste` to `"ctrl+v"`, commandline
applications won't be able to read a ctrl+v from the input. For these reasons,
we suggest `"ctrl+shift+c"` and `"ctrl+shift+v"`
### Setting the `startingDirectory` of WSL Profiles to `~`
By default, the `startingDirectory` of a profile is `%USERPROFILE%`
(`C:\Users\<YourUsername>`). This is a Windows path. However, for WSL, you might
want to use the WSL home path instead. At the time of writing (26decf1 / Nov.
1st, 2019), `startingDirectory` only accepts a Windows-style path, so setting it
to start within the WSL distro can be a little tricky.
Fortunately, with Windows 1903, the filesystems of WSL distros can easily be
addressed using the `\\wsl$\` prefix. For any WSL distro whose name is
`DistroName`, you can use `\\wsl$\DistroName` as a Windows path that points to
the root of that distro's filesystem.
For example, the following works as a profile to launch the "Ubuntu-18.04"
distro in it's home path:
```json
{
"name": "Ubuntu-18.04",
"commandline" : "wsl -d Ubuntu-18.04",
"startingDirectory" : "//wsl$/Ubuntu-18.04/home/<Your Ubuntu Username>",
}
```
⚠ This document has moved to [the Customize Settings section of the Windows Terminal documentation](https://docs.microsoft.com/windows/terminal/customize-settings/global-settings).

View File

@@ -1,91 +1 @@
# Windows Terminal User Documentation
NOTE: At the time of writing Windows Terminal is still under active development and many things will
change. If you notice an error in the docs, please raise an issue. Or better yet, please file a PR with an appropriate update!
## Installing Windows Terminal
### From Source Code
To compile Windows Terminal yourself using the source code, follow the instructions in the [README](/README.md#developer-guidance).
### From the Microsoft Store
1. Make sure you have upgraded to the current Windows 10 release (at least build `1903`). To determine your build number, see [winver](https://docs.microsoft.com/en-us/windows/client-management/windows-version-search).
2. Open the Windows Terminal listing in the [Microsoft Store](https://aka.ms/install-terminal).
3. Review the minimum system requirements to confirm you can successfully install Windows Terminal.
4. Click `Get` to begin the installation process.
## Starting Windows Terminal
1. Locate the _Windows Terminal_ app in your Start menu.
2. Click _Windows Terminal_ to launch the app. If you need administrative privileges, right-click the entry and click `Run as administrator`. Alternatively, you can highlight the app and press `Ctrl`+`Shift`+`Enter`.
NOTE: The default shell is PowerShell; you can change this using the _Running a Different Shell_ procedure.
### Command line options
Windows Terminal has implemented a rich set of command-line options in part as response to issue [#607](https://github.com/microsoft/terminal/issues/607). See [UsingCommandlineArguments.md](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingCommandlineArguments.md) for details.
## Multiple Tabs
Additional shells can be started by hitting the `+` button from the tab bar -- a new instance of the
default shell is displayed (default shortcut: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>1</kbd>).
## Running a Different Shell
Note: This section assumes you already have _Windows Subsystem for Linux_ (WSL) installed. For more information, see [the installation guide](https://docs.microsoft.com/en-us/windows/wsl/install-win10).
Windows Terminal uses PowerShell as its default shell. You can also use Windows Terminal to launch other shells, such as `cmd.exe` or WSL's `bash`:
1. In the tab bar, click the `⌵` button to view the available shells.
2. Choose your shell from the dropdown list. The new shell session will open in a new tab.
To customize the shell list, see the _Configuring Windows Terminal_ section below.
## Starting a new PowerShell tab with admin privilege
There is no current plan to support this feature for security reasons. See issue [#632](https://github.com/microsoft/terminal/issues/632)
## Selecting and Copying Text in Windows Terminal
As in ConHost, a selection can be made by left-clicking and dragging the mouse across the terminal. This is a line selection by default, meaning that the selection will wrap to the end of the line and the beginning of the next one. You can select in block mode by holding down the <kbd>Alt</kbd> key when starting a selection.
To copy the text to your clipboard, you can right-click the terminal when a selection is active. As of [#1224](https://github.com/microsoft/terminal/pull/1224) (first available in Windows Terminal v0.4), the Windows Terminal now supports HTML copy. The HTML is automatically copied to your clipboard along with the regular text in any copy operation.
If there is not an active selection, a right-click will paste the text content from your clipboard to the terminal.
Copy and paste operations can also be keybound. For more information on how to bind keys, see [Using Json Settings](UsingJsonSettings.md#adding-copy-and-paste-keybindings).
> 👉 **Note**: If you have the `copyOnSelect` global setting enabled, a selection will persist and immediately copy the selected text to your clipboard. Right-clicking will always paste your clipboard data.
## Add a "Open Windows Terminal Here" to File Explorer
Not currently supported "out of the box" (See issue [#1060](https://github.com/microsoft/terminal/issues/1060)). However, you can open Windows Terminal in current directory by typing `wt -d .` in the Explorer address bar.
## Configuring Windows Terminal
All Windows Terminal settings are currently managed using the `settings.json` file, located within `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState`.
To open the settings file from Windows Terminal:
1. Click the `⌵` button in the top bar.
2. From the dropdown list, click `Settings`. You can also use a shortcut: <kbd>Ctrl</kbd>+<kbd>,</kbd>.
3. Your default `json` editor will open the settings file.
For an introduction to the various settings, see [Using Json Settings](UsingJsonSettings.md). The list of valid settings can be found in the [settings.json documentation](../cascadia/SettingsSchema.md) section.
## Tips and Tricks
1. In PowerShell you can discover if the Windows Terminal is being used by checking for the existence of the environment variable `WT_SESSION`.
Under pwsh you can also use
`(Get-Process -Id $pid).Parent.ProcessName -eq 'WindowsTerminal'`
(ref [https://twitter.com/r_keith_hill/status/1142871145852440576](https://twitter.com/r_keith_hill/status/1142871145852440576))
2. Terminal zoom can be changed by holding <kbd>Ctrl</kbd> and scrolling with mouse.
3. Background opacity can be changed by holding <kbd>Ctrl</kbd>+<kbd>Shift</kbd> and scrolling with mouse. Note that acrylic transparency is limited by the OS only to focused windows.
4. Open Windows Terminal in current directory by typing `wt -d .` in the address bar.
5. Pin the Windows Terminal to the taskbar. Now it can be launched using the Windows shortcut <kbd>Win</kbd>+<kbd>Number</kbd> (e.g. <kbd>Win</kbd>+<kbd>1</kbd> or any other number based on the position in the taskbar!). Press <kbd>Win</kbd>+<kbd>Shift</kbd>+<kbd>Number</kbd> to always launch a new window.
6. Please add more Tips and Tricks.
⚠ Our user-facing documentation has moved to the [Windows Terminal documentation page](https://docs.microsoft.com/windows/terminal/).

View File

@@ -0,0 +1,7 @@
### Notes for Future Maintainers
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.

View File

@@ -0,0 +1,13 @@
{"Registrations":[
{
"component": {
"type": "git",
"git": {
"repositoryUrl": "https://gitlab.freedesktop.org/xorg/app/rgb.git",
"commitHash": "97820e748eb496a1f6d3fc3bf89688f0ce1f64f9"
}
}
}
],
"Version": 1
}

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -175,6 +175,23 @@ size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
return runPos - _list.cbegin();
}
// Routine Description:
// - Finds the hyperlink IDs present in this row and returns them
// Return value:
// - An unordered set containing the hyperlink IDs present in this row
std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
{
std::unordered_set<uint16_t> ids;
for (const auto& run : _list)
{
if (run.GetAttributes().IsHyperlink())
{
ids.emplace(run.GetAttributes().GetHyperlinkId());
}
}
return ids;
}
// Routine Description:
// - Sets the attributes (colors) of all character positions from the given position through the end of the row.
// Arguments:

View File

@@ -41,6 +41,8 @@ public:
size_t FindAttrIndex(const size_t index,
size_t* const pApplies) const;
std::unordered_set<uint16_t> GetHyperlinks();
bool SetAttrToEnd(const UINT iStart, const TextAttribute attr);
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept;

View File

@@ -88,16 +88,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.
// Return Value:
// - the foreground and background colors that should be displayed.
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode) const noexcept
const bool reverseScreenMode,
const bool blinkingIsFaint) const noexcept
{
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold());
auto bg = _background.GetColor(colorTable, defaultBgColor);
if (IsFaint())
if (IsFaint() || (IsBlinking() && blinkingIsFaint))
{
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
}
@@ -112,6 +114,17 @@ std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<
return { fg, bg };
}
// Method description:
// - Tells us whether the text is a hyperlink or not
// Return value:
// - True if it is a hyperlink, false otherwise
bool TextAttribute::IsHyperlink() const noexcept
{
// All non-hyperlink text have a default hyperlinkId of 0 while
// all hyperlink text have a non-zero hyperlinkId
return _hyperlinkId != 0;
}
TextColor TextAttribute::GetForeground() const noexcept
{
return _foreground;
@@ -122,6 +135,15 @@ TextColor TextAttribute::GetBackground() const noexcept
return _background;
}
// Method description:
// - Retrieves the hyperlink ID of the text
// Return value:
// - The hyperlink ID
uint16_t TextAttribute::GetHyperlinkId() const noexcept
{
return _hyperlinkId;
}
void TextAttribute::SetForeground(const TextColor foreground) noexcept
{
_foreground = foreground;
@@ -174,6 +196,15 @@ void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground)
}
}
// Method description:
// - Sets the hyperlink ID of the text
// Arguments:
// - id - the id we wish to set
void TextAttribute::SetHyperlinkId(uint16_t id) noexcept
{
_hyperlinkId = id;
}
bool TextAttribute::IsLeadingByte() const noexcept
{
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
@@ -249,6 +280,11 @@ bool TextAttribute::IsUnderlined() const noexcept
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Underlined);
}
bool TextAttribute::IsDoublyUnderlined() const noexcept
{
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::DoublyUnderlined);
}
bool TextAttribute::IsOverlined() const noexcept
{
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL);
@@ -294,6 +330,11 @@ void TextAttribute::SetUnderlined(bool isUnderlined) noexcept
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Underlined, isUnderlined);
}
void TextAttribute::SetDoublyUnderlined(bool isDoublyUnderlined) noexcept
{
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::DoublyUnderlined, isDoublyUnderlined);
}
void TextAttribute::SetOverlined(bool isOverlined) noexcept
{
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL, isOverlined);
@@ -326,6 +367,14 @@ void TextAttribute::SetDefaultBackground() noexcept
_background = TextColor();
}
// Method description:
// - Resets only the meta and extended attributes
void TextAttribute::SetDefaultMetaAttrs() noexcept
{
_extendedAttrs = ExtendedAttributes::Normal;
_wAttrLegacy = 0;
}
// Method Description:
// - Returns true if this attribute indicates its background is the "default"
// background. Its _rgbBackground will contain the actual value of the
@@ -346,6 +395,6 @@ bool TextAttribute::BackgroundIsDefault() const noexcept
// requires for most erasing and filling operations.
void TextAttribute::SetStandardErase() noexcept
{
_extendedAttrs = ExtendedAttributes::Normal;
_wAttrLegacy = 0;
SetDefaultMetaAttrs();
_hyperlinkId = 0;
}

View File

@@ -36,7 +36,8 @@ public:
_wAttrLegacy{ 0 },
_foreground{},
_background{},
_extendedAttrs{ ExtendedAttributes::Normal }
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
{
}
@@ -44,7 +45,8 @@ public:
_wAttrLegacy{ gsl::narrow_cast<WORD>(wLegacyAttr & META_ATTRS) },
_foreground{ s_LegacyIndexOrDefault(wLegacyAttr & FG_ATTRS, s_legacyDefaultForeground) },
_background{ s_LegacyIndexOrDefault((wLegacyAttr & BG_ATTRS) >> 4, s_legacyDefaultBackground) },
_extendedAttrs{ ExtendedAttributes::Normal }
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
{
// If we're given lead/trailing byte information with the legacy color, strip it.
WI_ClearAllFlags(_wAttrLegacy, COMMON_LVB_SBCSDBCS);
@@ -55,7 +57,8 @@ public:
_wAttrLegacy{ 0 },
_foreground{ rgbForeground },
_background{ rgbBackground },
_extendedAttrs{ ExtendedAttributes::Normal }
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
{
}
@@ -66,7 +69,8 @@ public:
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode = false) const noexcept;
const bool reverseScreenMode = false,
const bool blinkingIsFaint = false) const noexcept;
bool IsLeadingByte() const noexcept;
bool IsTrailingByte() const noexcept;
@@ -95,6 +99,7 @@ public:
bool IsInvisible() const noexcept;
bool IsCrossedOut() const noexcept;
bool IsUnderlined() const noexcept;
bool IsDoublyUnderlined() const noexcept;
bool IsOverlined() const noexcept;
bool IsReverseVideo() const noexcept;
@@ -105,13 +110,17 @@ public:
void SetInvisible(bool isInvisible) noexcept;
void SetCrossedOut(bool isCrossedOut) noexcept;
void SetUnderlined(bool isUnderlined) noexcept;
void SetDoublyUnderlined(bool isDoublyUnderlined) noexcept;
void SetOverlined(bool isOverlined) noexcept;
void SetReverseVideo(bool isReversed) noexcept;
ExtendedAttributes GetExtendedAttributes() const noexcept;
bool IsHyperlink() const noexcept;
TextColor GetForeground() const noexcept;
TextColor GetBackground() const noexcept;
uint16_t GetHyperlinkId() const noexcept;
void SetForeground(const TextColor foreground) noexcept;
void SetBackground(const TextColor background) noexcept;
void SetForeground(const COLORREF rgbForeground) noexcept;
@@ -121,9 +130,11 @@ public:
void SetIndexedForeground256(const BYTE fgIndex) noexcept;
void SetIndexedBackground256(const BYTE bgIndex) noexcept;
void SetColor(const COLORREF rgbColor, const bool fIsForeground) noexcept;
void SetHyperlinkId(uint16_t id) noexcept;
void SetDefaultForeground() noexcept;
void SetDefaultBackground() noexcept;
void SetDefaultMetaAttrs() noexcept;
bool BackgroundIsDefault() const noexcept;
@@ -141,11 +152,14 @@ public:
return !IsAnyGridLineEnabled() && // grid lines have a visual representation
// crossed out, doubly and singly underlined have a visual representation
WI_AreAllFlagsClear(_extendedAttrs, ExtendedAttributes::CrossedOut | ExtendedAttributes::DoublyUnderlined | ExtendedAttributes::Underlined) &&
// hyperlinks have a visual representation
!IsHyperlink() &&
// all other attributes do not have a visual representation
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
((checkForeground && _foreground == other._foreground) ||
(!checkForeground && _background == other._background)) &&
_extendedAttrs == other._extendedAttrs;
_extendedAttrs == other._extendedAttrs &&
IsHyperlink() == other.IsHyperlink();
}
constexpr bool IsAnyGridLineEnabled() const noexcept
@@ -167,6 +181,8 @@ private:
TextColor _background;
ExtendedAttributes _extendedAttrs;
uint16_t _hyperlinkId;
#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class TextAttributeTests;
@@ -180,7 +196,7 @@ private:
// 4 for _foreground
// 4 for _background
// 1 for _extendedAttrs
static_assert(sizeof(TextAttribute) <= 11 * sizeof(BYTE), "We should only need 11B for an entire TextColor. Any more than that is just waste");
static_assert(sizeof(TextAttribute) <= 13 * sizeof(BYTE), "We should only need 13B for an entire TextAttribute. We may need to increment this in the future as we add additional attributes");
enum class TextAttributeBehavior
{
@@ -194,7 +210,8 @@ constexpr bool operator==(const TextAttribute& a, const TextAttribute& b) noexce
return a._wAttrLegacy == b._wAttrLegacy &&
a._foreground == b._foreground &&
a._background == b._background &&
a._extendedAttrs == b._extendedAttrs;
a._extendedAttrs == b._extendedAttrs &&
a._hyperlinkId == b._hyperlinkId;
}
constexpr bool operator!=(const TextAttribute& a, const TextAttribute& b) noexcept

View File

@@ -35,7 +35,7 @@ void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
// - erases key and its associated data from the storage
// Arguments:
// - key - the key to remove
void UnicodeStorage::Erase(const key_type key)
void UnicodeStorage::Erase(const key_type key) noexcept
{
_map.erase(key);
}

View File

@@ -53,7 +53,7 @@ public:
void StoreGlyph(const key_type key, const mapped_type& glyph);
void Erase(const key_type key);
void Erase(const key_type key) noexcept;
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);

View File

@@ -34,7 +34,8 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
_storage{},
_unicodeStorage{},
_renderTarget{ renderTarget },
_size{}
_size{},
_currentHyperlinkId{ 1 }
{
// initialize ROWs
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
@@ -551,7 +552,10 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
// to the logical position 0 in the window (cursor coordinates and all other coordinates).
_renderTarget.TriggerCircling();
// First, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed.
// Prune hyperlinks to delete obsolete references
_PruneHyperlinks();
// Second, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed.
auto fillAttributes = _currentAttributes;
if (inVtMode)
{
@@ -992,19 +996,29 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view
// so the words in the example include ["word ", "other "]
// NOTE: the start anchor (this one) is inclusive, whereas the end anchor (GetWordEnd) is exclusive
// can't expand left
if (target.X == GetSize().Left())
#pragma warning(suppress : 26496)
// GH#7664: Treat EndExclusive as EndInclusive so
// that it actually points to a space in the buffer
auto copy{ target };
const auto bufferSize{ GetSize() };
if (target == bufferSize.Origin())
{
// can't expand left
return target;
}
else if (target == bufferSize.EndExclusive())
{
// treat EndExclusive as EndInclusive
copy = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() };
}
if (accessibilityMode)
{
return _GetWordStartForAccessibility(target, wordDelimiters);
return _GetWordStartForAccessibility(copy, wordDelimiters);
}
else
{
return _GetWordStartForSelection(target, wordDelimiters);
return _GetWordStartForSelection(copy, wordDelimiters);
}
}
@@ -1104,9 +1118,16 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
// so the words in the example include ["word ", "other "]
// NOTE: the end anchor (this one) is exclusive, whereas the start anchor (GetWordStart) is inclusive
// Already at the end. Can't move forward.
if (target == GetSize().EndExclusive())
{
return target;
}
if (accessibilityMode)
{
return _GetWordEndForAccessibility(target, wordDelimiters);
const auto lastCharPos{ GetLastNonSpaceCharacter() };
return _GetWordEndForAccessibility(target, wordDelimiters, lastCharPos);
}
else
{
@@ -1119,13 +1140,20 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
// Arguments:
// - target - a COORD on the word you are currently on
// - wordDelimiters - what characters are we considering for the separation of words
// - lastCharPos - the position of the last nonspace character in the text buffer (to improve performance)
// Return Value:
// - The COORD for the first character of the next readable "word". If no next word, return one past the end of the buffer
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const
{
const auto bufferSize = GetSize();
COORD result = target;
// Check if we're already on/past the last RegularChar
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
{
return bufferSize.EndExclusive();
}
// ignore right boundary. Continue through readable text found
while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
{
@@ -1135,6 +1163,12 @@ const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const st
}
}
// we are already on/past the last RegularChar
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
{
return bufferSize.EndExclusive();
}
// make sure we expand to the beginning of the NEXT word
while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
{
@@ -1185,6 +1219,46 @@ const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::w
return result;
}
void TextBuffer::_PruneHyperlinks()
{
// Check the old first row for hyperlink references
// If there are any, search the entire buffer for the same reference
// If the buffer does not contain the same reference, we can remove that hyperlink from our map
// This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around
// Get all the hyperlink references in the row we're erasing
auto firstRowRefs = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
if (!firstRowRefs.empty())
{
const auto total = TotalRowCount();
// Loop through all the rows in the buffer except the first row -
// we have found all hyperlink references in the first row and put them in refs,
// now we need to search the rest of the buffer (i.e. all the rows except the first)
// to see if those references are anywhere else
for (size_t i = 1; i != total; ++i)
{
const auto nextRowRefs = GetRowByOffset(i).GetAttrRow().GetHyperlinks();
for (auto id : nextRowRefs)
{
if (firstRowRefs.find(id) != firstRowRefs.end())
{
firstRowRefs.erase(id);
}
}
if (firstRowRefs.empty())
{
// No more hyperlink references left to search for, terminate early
break;
}
}
}
// Now delete obsolete references from our map
for (auto hyperlinkReference : firstRowRefs)
{
RemoveHyperlinkFromMap(hyperlinkReference);
}
}
// Method Description:
// - Update pos to be the position of the first character of the next word. This is used for accessibility
// Arguments:
@@ -1196,38 +1270,16 @@ const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::w
// - pos - The COORD for the first character on the "word" (inclusive)
bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const
{
auto copy = pos;
const auto bufferSize = GetSize();
// move to the beginning of the next word
// NOTE: _GetWordEnd...() returns the exclusive position of the "end of the word"
// This is also the inclusive start of the next word.
auto copy{ _GetWordEndForAccessibility(pos, wordDelimiters, lastCharPos) };
// started on a word, continue until the end of the word
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
{
if (!bufferSize.IncrementInBounds(copy))
{
// last char in buffer is a RegularChar
// thus there is no next word
return false;
}
}
// we are already on/past the last RegularChar
if (bufferSize.CompareInBounds(copy, lastCharPos) >= 0)
if (copy == GetSize().EndExclusive())
{
return false;
}
// on whitespace, continue until the beginning of the next word
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
{
if (!bufferSize.IncrementInBounds(copy))
{
// last char in buffer is a DelimiterChar or ControlChar
// there is no next word
return false;
}
}
// successful move, copy result out
pos = copy;
return true;
}
@@ -1242,33 +1294,17 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite
// - pos - The COORD for the first character on the "word" (inclusive)
bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const
{
auto copy = pos;
auto bufferSize = GetSize();
// move to the beginning of the current word
auto copy{ GetWordStart(pos, wordDelimiters, true) };
// started on whitespace/delimiter, continue until the end of the previous word
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
if (!GetSize().DecrementInBounds(copy, true))
{
if (!bufferSize.DecrementInBounds(copy))
{
// first char in buffer is a DelimiterChar or ControlChar
// there is no previous word
return false;
}
// can't move behind current word
return false;
}
// on a word, continue until the beginning of the word
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
{
if (!bufferSize.DecrementInBounds(copy))
{
// first char in buffer is a RegularChar
// there is no previous word
return false;
}
}
// successful move, copy result out
pos = copy;
// move to the beginning of the previous word
pos = GetWordStart(copy, wordDelimiters, true);
return true;
}
@@ -1281,8 +1317,13 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
const til::point TextBuffer::GetGlyphStart(const til::point pos) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
if (resultPos == bufferSize.EndExclusive())
{
bufferSize.DecrementInBounds(resultPos, true);
}
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
{
bufferSize.DecrementInBounds(resultPos, true);
@@ -1323,9 +1364,15 @@ const til::point TextBuffer::GetGlyphEnd(const til::point pos) const
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
if (resultPos == GetSize().EndExclusive())
{
// we're already at the end
return false;
}
// try to move. If we can't, we're done.
const auto bufferSize = GetSize();
const bool success = bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
{
@@ -1340,20 +1387,19 @@ bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) con
// - Update pos to be the beginning of the previous glyph/character. This is used for accessibility
// Arguments:
// - pos - a COORD on the word you are currently on
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
// Return Value:
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
// - pos - The COORD for the first cell of the previous glyph (inclusive)
bool TextBuffer::MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive) const
bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
{
COORD resultPos = pos;
// try to move. If we can't, we're done.
const auto bufferSize = GetSize();
const bool success = bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
const bool success = bufferSize.DecrementInBounds(resultPos, true);
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
{
bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
bufferSize.DecrementInBounds(resultPos, true);
}
pos = resultPos;
@@ -2142,6 +2188,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// Finish copying remaining parameters from the old text buffer to the new one
newBuffer.CopyProperties(oldBuffer);
newBuffer.CopyHyperlinkMaps(oldBuffer);
// If we found where to put the cursor while placing characters into the buffer,
// just put the cursor there. Otherwise we have to advance manually.
@@ -2207,3 +2254,104 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
return hr;
}
// Method Description:
// - Adds or updates a hyperlink in our hyperlink table
// Arguments:
// - The hyperlink URI, the hyperlink id (could be new or old)
void TextBuffer::AddHyperlinkToMap(std::wstring_view uri, uint16_t id)
{
_hyperlinkMap[id] = uri;
}
// Method Description:
// - Retrieves the URI associated with a particular hyperlink ID
// Arguments:
// - The hyperlink ID
// Return Value:
// - The URI
std::wstring TextBuffer::GetHyperlinkUriFromId(uint16_t id) const
{
return _hyperlinkMap.at(id);
}
// Method description:
// - Provides the hyperlink ID to be assigned as a text attribute, based on the optional custom id provided
// Arguments:
// - The user-defined id
// Return value:
// - The internal hyperlink ID
uint16_t TextBuffer::GetHyperlinkId(std::wstring_view params)
{
uint16_t id = 0;
if (params.empty())
{
// no custom id specified, return our internal count
id = _currentHyperlinkId;
++_currentHyperlinkId;
}
else
{
// assign _currentHyperlinkId if the custom id does not already exist
const auto result = _hyperlinkCustomIdMap.emplace(params, _currentHyperlinkId);
if (result.second)
{
// the custom id did not already exist
++_currentHyperlinkId;
}
id = (*(result.first)).second;
}
// _currentHyperlinkId could overflow, make sure its not 0
if (_currentHyperlinkId == 0)
{
++_currentHyperlinkId;
}
return id;
}
// Method Description:
// - Removes a hyperlink from the hyperlink map and the associated
// user defined id from the custom id map (if there is one)
// Arguments:
// - The ID of the hyperlink to be removed
void TextBuffer::RemoveHyperlinkFromMap(uint16_t id)
{
_hyperlinkMap.erase(id);
for (const auto& customIdPair : _hyperlinkCustomIdMap)
{
if (customIdPair.second == id)
{
_hyperlinkCustomIdMap.erase(customIdPair.first);
break;
}
}
}
// Method Description:
// - Obtains the custom ID, if there was one, associated with the
// uint16_t id of a hyperlink
// Arguments:
// - The uint16_t id of the hyperlink
// Return Value:
// - The custom ID if there was one, empty string otherwise
std::wstring TextBuffer::GetCustomIdFromId(uint16_t id) const
{
for (auto customIdPair : _hyperlinkCustomIdMap)
{
if (customIdPair.second == id)
{
return customIdPair.first;
}
}
return {};
}
// Method Description:
// - Copies the hyperlink/customID maps of the old buffer into this one
// Arguments:
// - The other buffer
void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other)
{
_hyperlinkMap = other._hyperlinkMap;
_hyperlinkCustomIdMap = other._hyperlinkCustomIdMap;
}

View File

@@ -137,10 +137,17 @@ public:
const til::point GetGlyphStart(const til::point pos) const;
const til::point GetGlyphEnd(const til::point pos) const;
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
bool MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive = false) const;
bool MoveToPreviousGlyph(til::point& pos) const;
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
uint16_t GetHyperlinkId(std::wstring_view params);
void RemoveHyperlinkFromMap(uint16_t id);
std::wstring GetCustomIdFromId(uint16_t id) const;
void CopyHyperlinkMaps(const TextBuffer& OtherBuffer);
class TextAndColor
{
public:
@@ -188,6 +195,10 @@ private:
// storage location for glyphs that can't fit into the buffer normally
UnicodeStorage _unicodeStorage;
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
uint16_t _currentHyperlinkId;
void _RefreshRowIDs(std::optional<SHORT> newRowWidth);
Microsoft::Console::Render::IRenderTarget& _renderTarget;
@@ -213,9 +224,11 @@ private:
const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const;
const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const;
const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
void _PruneHyperlinks();
#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class UiaTextRangeTests;

View File

@@ -26,7 +26,7 @@
<Link>ProfileIcons\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>
<!-- Default Settings -->
<Content Include="$(OpenConsoleDir)src\cascadia\TerminalApp\defaults.json">
<Content Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsModel\defaults.json">
<DeploymentContent>true</DeploymentContent>
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>

View File

@@ -0,0 +1,293 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/ColorScheme.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "JsonTestClass.h"
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ColorSchemeTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(ColorSchemeTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(CanLayerColorScheme);
TEST_METHOD(LayerColorSchemeProperties);
TEST_METHOD(LayerColorSchemesOnArray);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
};
void ColorSchemeTests::CanLayerColorScheme()
{
const std::string scheme0String{ R"({
"name": "scheme0",
"foreground": "#000000",
"background": "#010101"
})" };
const std::string scheme1String{ R"({
"name": "scheme1",
"foreground": "#020202",
"background": "#030303"
})" };
const std::string scheme2String{ R"({
"name": "scheme0",
"foreground": "#040404",
"background": "#050505"
})" };
const std::string scheme3String{ R"({
// "name": "scheme3",
"foreground": "#060606",
"background": "#070707"
})" };
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
const auto scheme3Json = VerifyParseSucceeded(scheme3String);
const auto scheme0 = ColorScheme::FromJson(scheme0Json);
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme1Json));
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme3Json));
const auto scheme1 = ColorScheme::FromJson(scheme1Json);
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme0Json));
VERIFY_IS_TRUE(scheme1->ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme3Json));
const auto scheme3 = ColorScheme::FromJson(scheme3Json);
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme3Json));
}
void ColorSchemeTests::LayerColorSchemeProperties()
{
const std::string scheme0String{ R"({
"name": "scheme0",
"foreground": "#000000",
"background": "#010101",
"selectionBackground": "#010100",
"cursorColor": "#010001",
"red": "#010000",
"green": "#000100",
"blue": "#000001"
})" };
const std::string scheme1String{ R"({
"name": "scheme1",
"foreground": "#020202",
"background": "#030303",
"selectionBackground": "#020200",
"cursorColor": "#040004",
"red": "#020000",
"blue": "#000002"
})" };
const std::string scheme2String{ R"({
"name": "scheme0",
"foreground": "#040404",
"background": "#050505",
"selectionBackground": "#030300",
"cursorColor": "#060006",
"red": "#030000",
"green": "#000300"
})" };
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
auto scheme0 = ColorScheme::FromJson(scheme0Json);
VERIFY_ARE_EQUAL(L"scheme0", scheme0->_Name);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 0), scheme0->_SelectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 1), scheme0->_CursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 1), scheme0->_table[XTERM_BLUE_ATTR]);
Log::Comment(NoThrowString().Format(
L"Layering scheme1 on top of scheme0"));
scheme0->LayerJson(scheme1Json);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0->_Background);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 0), scheme0->_SelectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 4, 0, 4), scheme0->_CursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 2, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]);
Log::Comment(NoThrowString().Format(
L"Layering scheme2Json on top of (scheme0+scheme1)"));
scheme0->LayerJson(scheme2Json);
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 0), scheme0->_SelectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 0, 6), scheme0->_CursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 3, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 3, 0), scheme0->_table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]);
}
void ColorSchemeTests::LayerColorSchemesOnArray()
{
const std::string scheme0String{ R"({
"name": "scheme0",
"foreground": "#000000",
"background": "#010101"
})" };
const std::string scheme1String{ R"({
"name": "scheme1",
"foreground": "#020202",
"background": "#030303"
})" };
const std::string scheme2String{ R"({
"name": "scheme0",
"foreground": "#040404",
"background": "#050505"
})" };
const std::string scheme3String{ R"({
// by not providing a name, the scheme will have the name ""
"foreground": "#060606",
"background": "#070707"
})" };
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
const auto scheme3Json = VerifyParseSucceeded(scheme3String);
auto settings = winrt::make_self<CascadiaSettings>();
VERIFY_ARE_EQUAL(0u, settings->_globals->ColorSchemes().Size());
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
settings->_LayerOrCreateColorScheme(scheme0Json);
{
for (auto kv : settings->_globals->ColorSchemes())
{
Log::Comment(NoThrowString().Format(
L"kv:%s->%s", kv.Key().data(), kv.Value().Name().data()));
}
VERIFY_ARE_EQUAL(1u, settings->_globals->ColorSchemes().Size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background);
}
settings->_LayerOrCreateColorScheme(scheme1Json);
{
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background);
}
settings->_LayerOrCreateColorScheme(scheme2Json);
{
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background);
}
settings->_LayerOrCreateColorScheme(scheme3Json);
{
VERIFY_ARE_EQUAL(3u, settings->_globals->ColorSchemes().Size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L""));
auto scheme2Proj = settings->_globals->ColorSchemes().Lookup(L"");
auto scheme2 = winrt::get_self<ColorScheme>(scheme2Proj);
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background);
VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2->_Foreground);
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_Background);
}
}
}

View File

@@ -3,19 +3,19 @@
#include "pch.h"
#include "../TerminalApp/CascadiaSettings.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "JsonTestClass.h"
#include "TestUtils.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Windows::Foundation::Collections;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalAppLocalTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
@@ -61,25 +61,25 @@ namespace TerminalAppLocalTests
const auto commands1Json = VerifyParseSucceeded(commands1String);
const auto commands2Json = VerifyParseSucceeded(commands2String);
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(1u, commands.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
{
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(2u, commands.size());
VERIFY_ARE_EQUAL(2u, commands.Size());
{
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(4u, commands.size());
VERIFY_ARE_EQUAL(4u, commands.Size());
}
void CommandTests::LayerCommand()
@@ -95,13 +95,13 @@ namespace TerminalAppLocalTests
const auto commands2Json = VerifyParseSucceeded(commands2String);
const auto commands3Json = VerifyParseSucceeded(commands3String);
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_ARE_EQUAL(1u, commands.Size());
auto command = commands.Lookup(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
@@ -111,8 +111,8 @@ namespace TerminalAppLocalTests
{
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_ARE_EQUAL(1u, commands.Size());
auto command = commands.Lookup(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
@@ -121,8 +121,8 @@ namespace TerminalAppLocalTests
{
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_ARE_EQUAL(1u, commands.Size());
auto command = commands.Lookup(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
@@ -133,7 +133,7 @@ namespace TerminalAppLocalTests
// This last command should "unbind" the action.
auto warnings = implementation::Command::LayerJson(commands, commands3Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(0u, commands.size());
VERIFY_ARE_EQUAL(0u, commands.Size());
}
}
@@ -153,61 +153,61 @@ namespace TerminalAppLocalTests
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(5u, commands.size());
VERIFY_ARE_EQUAL(5u, commands.Size());
{
auto command = commands.at(L"command0");
auto command = commands.Lookup(L"command0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command1");
auto command = commands.Lookup(L"command1");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command2");
auto command = commands.Lookup(L"command2");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command4");
auto command = commands.Lookup(L"command4");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command5");
auto command = commands.Lookup(L"command5");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
}
void CommandTests::TestResourceKeyName()
@@ -217,17 +217,17 @@ namespace TerminalAppLocalTests
const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
// NOTE: We're relying on DuplicateTabCommandKey being defined as
// "Duplicate Tab" here. If that string changes in our resources,
// this test will break.
auto command = commands.at(L"Duplicate tab");
auto command = commands.Lookup(L"Duplicate tab");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
@@ -238,6 +238,14 @@ namespace TerminalAppLocalTests
void CommandTests::TestAutogeneratedName()
{
// Tests run in Helix can't report Skipped until GH#7286 is resolved.
// Set ignore flag to make Helix run completely overlook it.
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Ignore", L"True")
END_TEST_METHOD_PROPERTIES()
// This test to be corrected as a part of GH#7281
// This test ensures that we'll correctly create commands for actions
// that don't have given names, pursuant to the spec in GH#6532.
@@ -257,45 +265,45 @@ namespace TerminalAppLocalTests
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
// There are only 3 commands here: all of the `"none"`, `"auto"`,
// `"foo"`, `null`, and <no args> bindings all generate the same action,
// which will generate just a single name for all of them.
VERIFY_ARE_EQUAL(3u, commands.size());
VERIFY_ARE_EQUAL(3u, commands.Size());
{
auto command = commands.at(L"Split pane");
auto command = commands.Lookup(L"Split pane");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"Split pane, direction: vertical");
auto command = commands.Lookup(L"Split pane, split: vertical");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
}
{
auto command = commands.at(L"Split pane, direction: horizontal");
auto command = commands.Lookup(L"Split pane, split: horizontal");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
}
}
void CommandTests::TestLayerOnAutogeneratedName()
@@ -307,21 +315,21 @@ namespace TerminalAppLocalTests
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
{
auto command = commands.at(L"Split pane");
auto command = commands.Lookup(L"Split pane");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,20 +3,20 @@
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
#include "../TerminalSettingsModel/ColorScheme.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../TerminalSettingsModel/KeyMapping.h"
#include "JsonTestClass.h"
#include "TestUtils.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalAppLocalTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
@@ -66,18 +66,18 @@ namespace TerminalAppLocalTests
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto appKeyBindings = winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
auto keymap = winrt::make_self<implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(2u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(4u, keymap->_keyShortcuts.size());
}
void KeyBindingsTests::LayerKeybindings()
@@ -90,18 +90,18 @@ namespace TerminalAppLocalTests
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto appKeyBindings = winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
auto keymap = winrt::make_self<implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(2u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size());
}
void KeyBindingsTests::UnbindKeybindings()
@@ -120,52 +120,52 @@ namespace TerminalAppLocalTests
const auto bindings4Json = VerifyParseSucceeded(bindings4String);
const auto bindings5Json = VerifyParseSucceeded(bindings5String);
auto appKeyBindings = winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
auto keymap = winrt::make_self<implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using `\"unbound\"` to unbind the key"));
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using `null` to unbind the key"));
// First add back a good binding
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
// Then try layering in the bad setting
appKeyBindings->LayerJson(bindings3Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings3Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using an unrecognized command to unbind the key"));
// First add back a good binding
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
// Then try layering in the bad setting
appKeyBindings->LayerJson(bindings4Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings4Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using a straight up invalid value to unbind the key"));
// First add back a good binding
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
// Then try layering in the bad setting
appKeyBindings->LayerJson(bindings5Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings5Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key that wasn't bound at all"));
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
}
void KeyBindingsTests::TestArbitraryArgs()
@@ -189,17 +189,17 @@ namespace TerminalAppLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(10u, appKeyBindings->_keyShortcuts.size());
auto keymap = winrt::make_self<implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(10u, keymap->_keyShortcuts.size());
{
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') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -210,7 +210,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -221,7 +221,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ false, true, true, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -232,7 +232,7 @@ namespace TerminalAppLocalTests
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') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -244,7 +244,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` parses args correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('T') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -258,7 +258,7 @@ namespace TerminalAppLocalTests
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') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -272,7 +272,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `copy` ignores args it doesn't understand"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -284,7 +284,7 @@ namespace TerminalAppLocalTests
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') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -296,7 +296,7 @@ namespace TerminalAppLocalTests
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') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -308,7 +308,7 @@ namespace TerminalAppLocalTests
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') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -329,56 +329,56 @@ namespace TerminalAppLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(5u, appKeyBindings->_keyShortcuts.size());
auto keymap = winrt::make_self<implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(5u, keymap->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
}
}
@@ -392,15 +392,15 @@ namespace TerminalAppLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, appKeyBindings->_keyShortcuts.size());
auto keymap = winrt::make_self<implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, keymap->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -409,7 +409,7 @@ namespace TerminalAppLocalTests
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -420,7 +420,7 @@ namespace TerminalAppLocalTests
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -437,15 +437,15 @@ namespace TerminalAppLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
auto keymap = winrt::make_self<implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value

View File

@@ -0,0 +1,308 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/ColorScheme.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "JsonTestClass.h"
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ProfileTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(ProfileTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(CanLayerProfile);
TEST_METHOD(LayerProfileProperties);
TEST_METHOD(LayerProfileIcon);
TEST_METHOD(LayerProfilesOnArray);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
};
void ProfileTests::CanLayerProfile()
{
const std::string profile0String{ R"({
"name" : "profile0",
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile1String{ R"({
"name" : "profile1",
"guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile2String{ R"({
"name" : "profile2",
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile3String{ R"({
"name" : "profile3"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
const auto profile0 = implementation::Profile::FromJson(profile0Json);
VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile1Json));
VERIFY_IS_TRUE(profile0->ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile3Json));
const auto profile1 = implementation::Profile::FromJson(profile1Json);
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile0Json));
// A profile _can_ be layered with itself, though what's the point?
VERIFY_IS_TRUE(profile1->ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile3Json));
const auto profile3 = implementation::Profile::FromJson(profile3Json);
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile0Json));
// A profile _can_ be layered with itself, though what's the point?
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile3Json));
}
void ProfileTests::LayerProfileProperties()
{
const std::string profile0String{ R"({
"name": "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"foreground": "#000000",
"background": "#010101",
"selectionBackground": "#010101"
})" };
const std::string profile1String{ R"({
"name": "profile1",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"foreground": "#020202",
"startingDirectory": "C:/"
})" };
const std::string profile2String{ R"({
"name": "profile2",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"foreground": "#030303",
"selectionBackground": "#020202"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
auto profile0 = implementation::Profile::FromJson(profile0Json);
VERIFY_IS_NOT_NULL(profile0->Foreground());
VERIFY_ARE_EQUAL(til::color(0, 0, 0), til::color{ profile0->Foreground().Value() });
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_IS_NOT_NULL(profile0->SelectionBackground());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->SelectionBackground().Value() });
VERIFY_ARE_EQUAL(L"profile0", profile0->Name());
VERIFY_IS_TRUE(profile0->StartingDirectory().empty());
Log::Comment(NoThrowString().Format(
L"Layering profile1 on top of profile0"));
profile0->LayerJson(profile1Json);
VERIFY_IS_NOT_NULL(profile0->Foreground());
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile0->Foreground().Value() });
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_ARE_EQUAL(L"profile1", profile0->Name());
VERIFY_IS_FALSE(profile0->StartingDirectory().empty());
VERIFY_ARE_EQUAL(L"C:/", profile0->StartingDirectory());
Log::Comment(NoThrowString().Format(
L"Layering profile2 on top of (profile0+profile1)"));
profile0->LayerJson(profile2Json);
VERIFY_IS_NOT_NULL(profile0->Foreground());
VERIFY_ARE_EQUAL(til::color(3, 3, 3), til::color{ profile0->Foreground().Value() });
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_IS_NOT_NULL(profile0->SelectionBackground());
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile0->SelectionBackground().Value() });
VERIFY_ARE_EQUAL(L"profile2", profile0->Name());
VERIFY_IS_FALSE(profile0->StartingDirectory().empty());
VERIFY_ARE_EQUAL(L"C:/", profile0->StartingDirectory());
}
void ProfileTests::LayerProfileIcon()
{
const std::string profile0String{ R"({
"name": "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"icon": "not-null.png"
})" };
const std::string profile1String{ R"({
"name": "profile1",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"icon": null
})" };
const std::string profile2String{ R"({
"name": "profile2",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile3String{ R"({
"name": "profile3",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"icon": "another-real.png"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
auto profile0 = implementation::Profile::FromJson(profile0Json);
VERIFY_IS_FALSE(profile0->Icon().empty());
VERIFY_ARE_EQUAL(L"not-null.png", profile0->Icon());
Log::Comment(NoThrowString().Format(
L"Verify that layering an object the key set to null will clear the key"));
profile0->LayerJson(profile1Json);
VERIFY_IS_TRUE(profile0->Icon().empty());
profile0->LayerJson(profile2Json);
VERIFY_IS_TRUE(profile0->Icon().empty());
profile0->LayerJson(profile3Json);
VERIFY_IS_FALSE(profile0->Icon().empty());
VERIFY_ARE_EQUAL(L"another-real.png", profile0->Icon());
Log::Comment(NoThrowString().Format(
L"Verify that layering an object _without_ the key will not clear the key"));
profile0->LayerJson(profile2Json);
VERIFY_IS_FALSE(profile0->Icon().empty());
VERIFY_ARE_EQUAL(L"another-real.png", profile0->Icon());
auto profile1 = implementation::Profile::FromJson(profile1Json);
VERIFY_IS_TRUE(profile1->Icon().empty());
profile1->LayerJson(profile3Json);
VERIFY_IS_FALSE(profile1->Icon().empty());
VERIFY_ARE_EQUAL(L"another-real.png", profile1->Icon());
}
void ProfileTests::LayerProfilesOnArray()
{
const std::string profile0String{ R"({
"name" : "profile0",
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile1String{ R"({
"name" : "profile1",
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile2String{ R"({
"name" : "profile2",
"guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile3String{ R"({
"name" : "profile3",
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile4String{ R"({
"name" : "profile4",
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
const auto profile4Json = VerifyParseSucceeded(profile4String);
auto settings = winrt::make_self<implementation::CascadiaSettings>();
VERIFY_ARE_EQUAL(0u, settings->_profiles.Size());
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile4Json));
settings->_LayerOrCreateProfile(profile0Json);
VERIFY_ARE_EQUAL(1u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
settings->_LayerOrCreateProfile(profile1Json);
VERIFY_ARE_EQUAL(2u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
settings->_LayerOrCreateProfile(profile2Json);
VERIFY_ARE_EQUAL(3u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile0", settings->_profiles.GetAt(0).Name());
settings->_LayerOrCreateProfile(profile3Json);
VERIFY_ARE_EQUAL(3u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile3", settings->_profiles.GetAt(0).Name());
settings->_LayerOrCreateProfile(profile4Json);
VERIFY_ARE_EQUAL(3u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile4", settings->_profiles.GetAt(0).Name());
}
}

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- A note about this project: We're building the test code dll from this
project, but it _MUST_ be run in conjunction with the TestHostApp project.
TestHostApp actually will build a TestHost executable and packaging bits
that we can use to run our tests. We need TestHostApp so that our
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into it's own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
<ProjectGuid>{CA5CAD1A-9B68-456A-B13E-C8218070DC42}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>SettingsModelLocalTests</RootNamespace>
<ProjectName>LocalTests_SettingsModel</ProjectName>
<TargetName>SettingsModel.LocalTests</TargetName>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
<!-- We'll manage our own OutDir/IntDir -->
<NoOutputRedirection>true</NoOutputRedirection>
</PropertyGroup>
<PropertyGroup>
<!-- Manually change our outdir to be in a subdirectory. We don't really want
to put our output in the bin root, because if we do, we'll copy
TerminalApp.winmd to the bin root, and then every subsequent mdmerge step
(in _any_ cppwinrt project) will automatically try to pick up
TerminalApp.winmd as a dependency (which is just wrong). This MUST be done
before importing common.build.pre.props -->
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)\src\cppwinrt.build.pre.props" />
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="JsonTestClass.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="ProfileTests.cpp" />
<ClCompile Include="ColorSchemeTests.cpp" />
<ClCompile Include="KeyBindingsTests.cpp" />
<ClCompile Include="CommandTests.cpp" />
<ClCompile Include="DeserializationTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
you want to use jsoncpp -->
<ClCompile Include="$(OpenConsoleDir)\dep\jsoncpp\jsoncpp.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\Microsoft.Terminal.Settings.ModelLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
<!-- If you don't reference these projects here, the
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
</ItemGroup>
<!-- ========================= Globals ======================== -->
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalDependencies>onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<PropertyGroup>
<GenerateManifest>true</GenerateManifest>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\common.build.tests.props" />
<PropertyGroup>
<_CppWinrtBinRoot>&quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\&quot;</_CppWinrtBinRoot>
<!-- From Microsoft.UI.Xaml.targets -->
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
</PropertyGroup>
<!-- We actually can just straight up reference MUX here, it's fine -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
</Project>

View File

@@ -12,19 +12,19 @@ Author(s):
Mike Griese (migrie) December-2019
--*/
class TerminalAppLocalTests::TestUtils
class TestUtils
{
public:
// Function Description:
// - This is a helper to retrieve the ActionAndArgs from the keybindings
// for a given chord.
// Arguments:
// - bindings: The AppKeyBindings to lookup the ActionAndArgs from.
// - keymap: The AppKeyBindings to lookup the ActionAndArgs from.
// - kc: The key chord to look up the bound ActionAndArgs for.
// Return Value:
// - The ActionAndArgs bound to the given key, or nullptr if nothing is bound to it.
static const winrt::TerminalApp::ActionAndArgs GetActionAndArgs(const winrt::TerminalApp::implementation::AppKeyBindings& bindings,
const winrt::Microsoft::Terminal::TerminalControl::KeyChord& kc)
static const winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs GetActionAndArgs(const winrt::Microsoft::Terminal::Settings::Model::KeyMapping& keymap,
const winrt::Microsoft::Terminal::TerminalControl::KeyChord& kc)
{
std::wstring buffer{ L"" };
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Ctrl))
@@ -42,12 +42,8 @@ public:
buffer += static_cast<wchar_t>(MapVirtualKeyW(kc.Vkey(), MAPVK_VK_TO_CHAR));
WEX::Logging::Log::Comment(WEX::Common::NoThrowString().Format(L"Looking for key:%s", buffer.c_str()));
const auto keyIter = bindings._keyShortcuts.find(kc);
VERIFY_IS_TRUE(keyIter != bindings._keyShortcuts.end(), L"Expected to find an action bound to the given KeyChord");
if (keyIter != bindings._keyShortcuts.end())
{
return keyIter->second;
}
return nullptr;
const auto action = keymap.TryLookup(kc);
VERIFY_IS_NOT_NULL(action, L"Expected to find an action bound to the given KeyChord");
return action;
};
};

View File

@@ -0,0 +1,65 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- precomp.h
Abstract:
- Contains external headers to include in the precompile phase of console build process.
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
Author(s):
- Carlos Zamora (cazamor) April 2019
--*/
#pragma once
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
// SDK definition of this function, so the only fix is to undef it.
// from WinBase.h
// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
#include <wil/cppwinrt.h>
#include <unknwn.h>
#include <hstring.h>
#include <WexTestClass.h>
#include <json.h>
#include "consoletaeftemplates.hpp"
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include "winrt/Windows.UI.Xaml.Markup.h"
#include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.ui.xaml.input.h>
#include <winrt/Windows.UI.Xaml.Markup.h>
#include <winrt/Windows.UI.Xaml.Documents.h>
#include <windows.ui.xaml.media.dxinterop.h>
#include <winrt/windows.applicationmodel.core.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"

View File

@@ -1,285 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
#include "JsonTestClass.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalAppLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ColorSchemeTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(ColorSchemeTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(CanLayerColorScheme);
TEST_METHOD(LayerColorSchemeProperties);
TEST_METHOD(LayerColorSchemesOnArray);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
};
void ColorSchemeTests::CanLayerColorScheme()
{
const std::string scheme0String{ R"({
"name": "scheme0",
"foreground": "#000000",
"background": "#010101"
})" };
const std::string scheme1String{ R"({
"name": "scheme1",
"foreground": "#020202",
"background": "#030303"
})" };
const std::string scheme2String{ R"({
"name": "scheme0",
"foreground": "#040404",
"background": "#050505"
})" };
const std::string scheme3String{ R"({
// "name": "scheme3",
"foreground": "#060606",
"background": "#070707"
})" };
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
const auto scheme3Json = VerifyParseSucceeded(scheme3String);
const auto scheme0 = ColorScheme::FromJson(scheme0Json);
VERIFY_IS_TRUE(scheme0.ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme0.ShouldBeLayered(scheme1Json));
VERIFY_IS_TRUE(scheme0.ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme0.ShouldBeLayered(scheme3Json));
const auto scheme1 = ColorScheme::FromJson(scheme1Json);
VERIFY_IS_FALSE(scheme1.ShouldBeLayered(scheme0Json));
VERIFY_IS_TRUE(scheme1.ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme1.ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme1.ShouldBeLayered(scheme3Json));
const auto scheme3 = ColorScheme::FromJson(scheme3Json);
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme3Json));
}
void ColorSchemeTests::LayerColorSchemeProperties()
{
const std::string scheme0String{ R"({
"name": "scheme0",
"foreground": "#000000",
"background": "#010101",
"selectionBackground": "#010100",
"cursorColor": "#010001",
"red": "#010000",
"green": "#000100",
"blue": "#000001"
})" };
const std::string scheme1String{ R"({
"name": "scheme1",
"foreground": "#020202",
"background": "#030303",
"selectionBackground": "#020200",
"cursorColor": "#040004",
"red": "#020000",
"blue": "#000002"
})" };
const std::string scheme2String{ R"({
"name": "scheme0",
"foreground": "#040404",
"background": "#050505",
"selectionBackground": "#030300",
"cursorColor": "#060006",
"red": "#030000",
"green": "#000300"
})" };
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
auto scheme0 = ColorScheme::FromJson(scheme0Json);
VERIFY_ARE_EQUAL(L"scheme0", scheme0._schemeName);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 1), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 1), scheme0._table[XTERM_BLUE_ATTR]);
Log::Comment(NoThrowString().Format(
L"Layering scheme1 on top of scheme0"));
scheme0.LayerJson(scheme1Json);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 4, 0, 4), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 2, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0._table[XTERM_BLUE_ATTR]);
Log::Comment(NoThrowString().Format(
L"Layering scheme2Json on top of (scheme0+scheme1)"));
scheme0.LayerJson(scheme2Json);
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 0, 6), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 3, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 3, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0._table[XTERM_BLUE_ATTR]);
}
void ColorSchemeTests::LayerColorSchemesOnArray()
{
const std::string scheme0String{ R"({
"name": "scheme0",
"foreground": "#000000",
"background": "#010101"
})" };
const std::string scheme1String{ R"({
"name": "scheme1",
"foreground": "#020202",
"background": "#030303"
})" };
const std::string scheme2String{ R"({
"name": "scheme0",
"foreground": "#040404",
"background": "#050505"
})" };
const std::string scheme3String{ R"({
// by not providing a name, the scheme will have the name ""
"foreground": "#060606",
"background": "#070707"
})" };
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
const auto scheme3Json = VerifyParseSucceeded(scheme3String);
CascadiaSettings settings;
VERIFY_ARE_EQUAL(0u, settings._globals.GetColorSchemes().size());
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
settings._LayerOrCreateColorScheme(scheme0Json);
{
for (auto& kv : settings._globals._colorSchemes)
{
Log::Comment(NoThrowString().Format(
L"kv:%s->%s", kv.first.data(), kv.second.GetName().data()));
}
VERIFY_ARE_EQUAL(1u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
}
settings._LayerOrCreateColorScheme(scheme1Json);
{
VERIFY_ARE_EQUAL(2u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
}
settings._LayerOrCreateColorScheme(scheme2Json);
{
VERIFY_ARE_EQUAL(2u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
}
settings._LayerOrCreateColorScheme(scheme3Json);
{
VERIFY_ARE_EQUAL(3u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"") != settings._globals._colorSchemes.end());
auto scheme2 = settings._globals._colorSchemes.find(L"")->second;
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2._defaultBackground);
}
}
}

View File

@@ -6,12 +6,12 @@
#include "../TerminalApp/TerminalPage.h"
#include "../TerminalApp/AppCommandlineArgs.h"
#include "../TerminalApp/ActionArgs.h"
using namespace WEX::Logging;
using namespace WEX::Common;
using namespace WEX::TestExecution;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::TerminalApp;
using namespace ::TerminalApp;
@@ -57,6 +57,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestSimpleExecuteCommandlineAction);
TEST_METHOD(TestMultipleCommandExecuteCommandlineAction);
TEST_METHOD(TestInvalidExecuteCommandlineAction);
TEST_METHOD(TestLaunchMode);
private:
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
@@ -1076,9 +1077,8 @@ namespace TerminalAppLocalTests
void CommandlineTest::TestSimpleExecuteCommandlineAction()
{
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
args->Commandline(L"new-tab");
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
ExecuteCommandlineArgs args{ L"new-tab" };
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
VERIFY_ARE_EQUAL(1u, actions.size());
auto actionAndArgs = actions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
@@ -1095,9 +1095,8 @@ namespace TerminalAppLocalTests
void CommandlineTest::TestMultipleCommandExecuteCommandlineAction()
{
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
args->Commandline(L"new-tab ; split-pane");
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
ExecuteCommandlineArgs args{ L"new-tab ; split-pane" };
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
VERIFY_ARE_EQUAL(2u, actions.size());
{
auto actionAndArgs = actions.at(0);
@@ -1129,10 +1128,100 @@ namespace TerminalAppLocalTests
void CommandlineTest::TestInvalidExecuteCommandlineAction()
{
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
// -H and -V cannot be combined.
args->Commandline(L"split-pane -H -V");
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
ExecuteCommandlineArgs args{ L"split-pane -H -V" };
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
VERIFY_ARE_EQUAL(0u, actions.size());
}
void CommandlineTest::TestLaunchMode()
{
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_FALSE(appArgs.GetLaunchMode().has_value());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"-F" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::FullscreenMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--fullscreen" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::FullscreenMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"-M" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--maximized" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"-f" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::FocusMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--focus" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::FocusMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"-fM" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedFocusMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--maximized", L"--focus" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedFocusMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--maximized", L"--focus", L"--focus" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedFocusMode);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--maximized", L"--focus", L"--maximized" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedFocusMode);
}
}
}

View File

@@ -1,308 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
#include "JsonTestClass.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalAppLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ProfileTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(ProfileTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(CanLayerProfile);
TEST_METHOD(LayerProfileProperties);
TEST_METHOD(LayerProfileIcon);
TEST_METHOD(LayerProfilesOnArray);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
};
void ProfileTests::CanLayerProfile()
{
const std::string profile0String{ R"({
"name" : "profile0",
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile1String{ R"({
"name" : "profile1",
"guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile2String{ R"({
"name" : "profile2",
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile3String{ R"({
"name" : "profile3"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
const auto profile0 = Profile::FromJson(profile0Json);
VERIFY_IS_FALSE(profile0.ShouldBeLayered(profile1Json));
VERIFY_IS_TRUE(profile0.ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile0.ShouldBeLayered(profile3Json));
const auto profile1 = Profile::FromJson(profile1Json);
VERIFY_IS_FALSE(profile1.ShouldBeLayered(profile0Json));
// A profile _can_ be layered with itself, though what's the point?
VERIFY_IS_TRUE(profile1.ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile1.ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile1.ShouldBeLayered(profile3Json));
const auto profile3 = Profile::FromJson(profile3Json);
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile0Json));
// A profile _can_ be layered with itself, though what's the point?
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile3Json));
}
void ProfileTests::LayerProfileProperties()
{
const std::string profile0String{ R"({
"name": "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"foreground": "#000000",
"background": "#010101",
"selectionBackground": "#010101"
})" };
const std::string profile1String{ R"({
"name": "profile1",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"foreground": "#020202",
"startingDirectory": "C:/"
})" };
const std::string profile2String{ R"({
"name": "profile2",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"foreground": "#030303",
"selectionBackground": "#020202"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
auto profile0 = Profile::FromJson(profile0Json);
VERIFY_IS_TRUE(profile0._defaultForeground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), profile0._defaultForeground.value());
VERIFY_IS_TRUE(profile0._defaultBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._defaultBackground.value());
VERIFY_IS_TRUE(profile0._selectionBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._selectionBackground.value());
VERIFY_ARE_EQUAL(L"profile0", profile0._name);
VERIFY_IS_FALSE(profile0._startingDirectory.has_value());
Log::Comment(NoThrowString().Format(
L"Layering profile1 on top of profile0"));
profile0.LayerJson(profile1Json);
VERIFY_IS_TRUE(profile0._defaultForeground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), profile0._defaultForeground.value());
VERIFY_IS_TRUE(profile0._defaultBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._defaultBackground.value());
VERIFY_IS_TRUE(profile0._selectionBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._selectionBackground.value());
VERIFY_ARE_EQUAL(L"profile1", profile0._name);
VERIFY_IS_TRUE(profile0._startingDirectory.has_value());
VERIFY_ARE_EQUAL(L"C:/", profile0._startingDirectory.value());
Log::Comment(NoThrowString().Format(
L"Layering profile2 on top of (profile0+profile1)"));
profile0.LayerJson(profile2Json);
VERIFY_IS_TRUE(profile0._defaultForeground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), profile0._defaultForeground.value());
VERIFY_IS_TRUE(profile0._defaultBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._defaultBackground.value());
VERIFY_IS_TRUE(profile0._selectionBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), profile0._selectionBackground.value());
VERIFY_ARE_EQUAL(L"profile2", profile0._name);
VERIFY_IS_TRUE(profile0._startingDirectory.has_value());
VERIFY_ARE_EQUAL(L"C:/", profile0._startingDirectory.value());
}
void ProfileTests::LayerProfileIcon()
{
const std::string profile0String{ R"({
"name": "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"icon": "not-null.png"
})" };
const std::string profile1String{ R"({
"name": "profile1",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"icon": null
})" };
const std::string profile2String{ R"({
"name": "profile2",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile3String{ R"({
"name": "profile3",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"icon": "another-real.png"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
auto profile0 = Profile::FromJson(profile0Json);
VERIFY_IS_TRUE(profile0._icon.has_value());
VERIFY_ARE_EQUAL(L"not-null.png", profile0._icon.value());
Log::Comment(NoThrowString().Format(
L"Verify that layering an object the key set to null will clear the key"));
profile0.LayerJson(profile1Json);
VERIFY_IS_FALSE(profile0._icon.has_value());
profile0.LayerJson(profile2Json);
VERIFY_IS_FALSE(profile0._icon.has_value());
profile0.LayerJson(profile3Json);
VERIFY_IS_TRUE(profile0._icon.has_value());
VERIFY_ARE_EQUAL(L"another-real.png", profile0._icon.value());
Log::Comment(NoThrowString().Format(
L"Verify that layering an object _without_ the key will not clear the key"));
profile0.LayerJson(profile2Json);
VERIFY_IS_TRUE(profile0._icon.has_value());
VERIFY_ARE_EQUAL(L"another-real.png", profile0._icon.value());
auto profile1 = Profile::FromJson(profile1Json);
VERIFY_IS_FALSE(profile1._icon.has_value());
profile1.LayerJson(profile3Json);
VERIFY_IS_TRUE(profile1._icon.has_value());
VERIFY_ARE_EQUAL(L"another-real.png", profile1._icon.value());
}
void ProfileTests::LayerProfilesOnArray()
{
const std::string profile0String{ R"({
"name" : "profile0",
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile1String{ R"({
"name" : "profile1",
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile2String{ R"({
"name" : "profile2",
"guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile3String{ R"({
"name" : "profile3",
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
})" };
const std::string profile4String{ R"({
"name" : "profile4",
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
})" };
const auto profile0Json = VerifyParseSucceeded(profile0String);
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
const auto profile4Json = VerifyParseSucceeded(profile4String);
CascadiaSettings settings;
VERIFY_ARE_EQUAL(0u, settings._profiles.size());
VERIFY_IS_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile4Json));
settings._LayerOrCreateProfile(profile0Json);
VERIFY_ARE_EQUAL(1u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
settings._LayerOrCreateProfile(profile1Json);
VERIFY_ARE_EQUAL(2u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
settings._LayerOrCreateProfile(profile2Json);
VERIFY_ARE_EQUAL(3u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile0", settings._profiles.at(0)._name);
settings._LayerOrCreateProfile(profile3Json);
VERIFY_ARE_EQUAL(3u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile3", settings._profiles.at(0)._name);
settings._LayerOrCreateProfile(profile4Json);
VERIFY_ARE_EQUAL(3u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile4", settings._profiles.at(0)._name);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,11 @@
#include "../TerminalApp/ShortcutActionDispatch.h"
#include "../TerminalApp/Tab.h"
#include "../CppWinrtTailored.h"
#include "JsonTestClass.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
@@ -26,7 +26,7 @@ namespace TerminalAppLocalTests
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class TabTests : public JsonTestClass
class TabTests
{
// For this set of tests, we need to activate some XAML content. For
// release builds, the application runs as a centennial application,
@@ -51,7 +51,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TryCreateSettingsType);
TEST_METHOD(TryCreateConnectionType);
TEST_METHOD(TryCreateXamlObjects);
TEST_METHOD(TryCreateTab);
TEST_METHOD(TryInitializePage);
TEST_METHOD(CreateSimpleTerminalXamlType);
TEST_METHOD(CreateTerminalMuxXamlType);
@@ -63,13 +64,17 @@ namespace TerminalAppLocalTests
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
return true;
}
private:
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
std::shared_ptr<CascadiaSettings> initialSettings);
CascadiaSettings initialSettings);
};
void TabTests::EnsureTestsActivate()
@@ -125,35 +130,6 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::TryCreateTab()
{
// If you leave the Tab ptr owned by the RunOnUIThread lambda, it
// will crash when the test tears down. Not totally clear why, but make
// sure it's owned outside the lambda
winrt::com_ptr<winrt::TerminalApp::implementation::Tab> newTab{ nullptr };
auto result = RunOnUIThread([&newTab]() {
// Try creating all of:
// 1. one of our pure c++ types (Profile)
// 2. one of our c++winrt types (TerminalSettings, EchoConnection)
// 3. one of our types that uses MUX/Xaml (TermControl).
// 4. one of our types that uses MUX/Xaml in this dll (Tab).
// Just creating all of them is enough to know that everything is working.
const auto profileGuid{ Utils::CreateGuid() };
TerminalSettings settings{};
VERIFY_IS_NOT_NULL(settings);
winrt::Microsoft::Terminal::TerminalConnection::EchoConnection conn{};
VERIFY_IS_NOT_NULL(conn);
winrt::Microsoft::Terminal::TerminalControl::TermControl term{ settings, conn };
VERIFY_IS_NOT_NULL(term);
newTab = winrt::make_self<winrt::TerminalApp::implementation::Tab>(profileGuid, term);
VERIFY_IS_NOT_NULL(newTab);
});
VERIFY_SUCCEEDED(result);
}
void TabTests::CreateSimpleTerminalXamlType()
{
winrt::com_ptr<winrt::TerminalApp::implementation::MinMaxCloseControl> mmcc{ nullptr };
@@ -213,7 +189,7 @@ namespace TerminalAppLocalTests
// Return Value:
// - <none>
void TabTests::_initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
std::shared_ptr<CascadiaSettings> initialSettings)
CascadiaSettings initialSettings)
{
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
@@ -276,229 +252,248 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::TryInitializePage()
{
// This is a very simple test to prove we can create settings and a
// TerminalPage and not only create them successfully, but also create a
// tab using those settings successfully.
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
}
void TabTests::TryDuplicateBadTab()
{
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
Log::Result(WEX::Logging::TestResults::Skipped);
return;
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _DuplicateTabViewItem on tab 1
// * No new tab should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
// // * Create a tab with a profile with GUID 1
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
// // * Try calling _DuplicateTabViewItem on tab 1
// // * No new tab should be created (and more importantly, the app should not crash)
// //
// // Created to test GH#2455
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson0{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile0",
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "historySize": 1
// },
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson1{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
// VerifyParseSucceeded(settingsJson0);
// auto settings0 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings0);
// settings0->_ParseJsonString(settingsJson0, false);
// settings0->LayerJson(settings0->_userSettings);
// settings0->_ValidateSettings();
CascadiaSettings settings1{ til::u8u16(settingsJson1) };
VERIFY_IS_NOT_NULL(settings1);
// VerifyParseSucceeded(settingsJson1);
// auto settings1 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings1);
// settings1->_ParseJsonString(settingsJson1, false);
// settings1->LayerJson(settings1->_userSettings);
// settings1->_ValidateSettings();
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 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}");
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
// // This is super wacky, but we can't just initialize the
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// // during TerminalPage::Create() below.
// //
// // Instead, create the winrt object, then get a com_ptr to the
// // implementation _from_ the winrt object. This seems to work, even if
// // it's weird.
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
// _initializeTerminalPage(page, settings0);
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// auto result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(L"Duplicate the first tab");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// Log::Comment(L"Duplicate the first tab");
// result = RunOnUIThread([&page]() {
// page->_DuplicateTabViewItem();
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = settings1;
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(
// L"Change the settings of the TerminalPage so the first profile is "
// L"no longer in the list of profiles"));
// result = RunOnUIThread([&page, settings1]() {
// page->_settings = settings1;
// });
// VERIFY_SUCCEEDED(result);
// Log::Comment(L"Duplicate the tab, and don't crash");
// result = RunOnUIThread([&page]() {
// page->_DuplicateTabViewItem();
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(L"Duplicate the tab, and don't crash");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
}
void TabTests::TryDuplicateBadPane()
{
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
Log::Result(WEX::Logging::TestResults::Skipped);
return;
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _SplitPane(Duplicate) on tab 1
// * No new pane should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
// // * Create a tab with a profile with GUID 1
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
// // * Try calling _SplitPane(Duplicate) on tab 1
// // * No new pane should be created (and more importantly, the app should not crash)
// //
// // Created to test GH#2455
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson0{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile0",
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "historySize": 1
// },
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson1{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
// VerifyParseSucceeded(settingsJson0);
// auto settings0 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings0);
// settings0->_ParseJsonString(settingsJson0, false);
// settings0->LayerJson(settings0->_userSettings);
// settings0->_ValidateSettings();
CascadiaSettings settings1{ til::u8u16(settingsJson1) };
VERIFY_IS_NOT_NULL(settings1);
// VerifyParseSucceeded(settingsJson1);
// auto settings1 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings1);
// settings1->_ParseJsonString(settingsJson1, false);
// settings1->LayerJson(settings1->_userSettings);
// settings1->_ValidateSettings();
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 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}");
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
// // This is super wacky, but we can't just initialize the
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// // during TerminalPage::Create() below.
// //
// // Instead, create the winrt object, then get a com_ptr to the
// // implementation _from_ the winrt object. This seems to work, even if
// // it's weird.
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
// _initializeTerminalPage(page, settings0);
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// auto result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
// result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
// Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
// result = RunOnUIThread([&page]() {
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = settings1;
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(
// L"Change the settings of the TerminalPage so the first profile is "
// L"no longer in the list of profiles"));
// result = RunOnUIThread([&page, settings1]() {
// page->_settings = settings1;
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
// Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
// result = RunOnUIThread([&page]() {
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2,
tab->GetLeafPaneCount(),
L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(2,
// tab->_GetLeafPaneCount(),
// L"We should gracefully do nothing here - the profile no longer exists.");
// });
// VERIFY_SUCCEEDED(result);
// auto cleanup = wil::scope_exit([] {
// auto result = RunOnUIThread([]() {
// // There's something causing us to crash north of
// // TSFInputControl::NotifyEnter, or LayoutRequested. It's very
// // unclear what that issue is. Since these tests don't run in
// // CI, simply log a message so that the dev running these tests
// // knows it's expected.
// Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
// });
// VERIFY_SUCCEEDED(result);
// });
auto cleanup = wil::scope_exit([] {
auto result = RunOnUIThread([]() {
// There's something causing us to crash north of
// TSFInputControl::NotifyEnter, or LayoutRequested. It's very
// unclear what that issue is. Since these tests don't run in
// CI, simply log a message so that the dev running these tests
// knows it's expected.
Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
});
VERIFY_SUCCEEDED(result);
});
}
}

View File

@@ -49,7 +49,6 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="JsonTestClass.h" />
<ClInclude Include="CppWinrtTailored.h" />
</ItemGroup>
@@ -57,31 +56,23 @@
<ItemGroup>
<ClCompile Include="CommandlineTest.cpp" />
<ClCompile Include="SettingsTests.cpp" />
<ClCompile Include="ProfileTests.cpp" />
<ClCompile Include="ColorSchemeTests.cpp" />
<ClCompile Include="KeyBindingsTests.cpp" />
<ClCompile Include="CommandTests.cpp" />
<ClCompile Include="TabTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
you want to use jsoncpp -->
<ClCompile Include="$(OpenConsoleDir)\dep\jsoncpp\jsoncpp.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\TerminalAppLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
<!-- If you don't reference these projects here, the
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\dll\TerminalApp.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
</ItemGroup>
<!-- ========================= Globals ======================== -->
@@ -89,7 +80,7 @@
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->

View File

@@ -96,10 +96,14 @@
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj">
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\dll\TerminalApp.vcxproj">
<Project>{ca5cad1a-44bd-4ac7-ac72-f16e576fdd12}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj">
<Project>{CA5CAD1A-082C-4476-9F33-94B339494076}</Project>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
@@ -153,6 +157,10 @@
<Copy SourceFiles="$(_TestBinRoot)\LocalTests_TerminalApp\TerminalApp.LocalTests.dll"
DestinationFiles="$(TargetDir)\TerminalApp.LocalTests.dll" />
<!-- Copy our test code from LocalTests_TerminalApp into this directory -->
<Copy SourceFiles="$(_TestBinRoot)\LocalTests_SettingsModel\SettingsModel.LocalTests.dll"
DestinationFiles="$(TargetDir)\SettingsModel.LocalTests.dll" />
<!-- Copy some dlls which TerminalConnection is dependent upon that didn't
get rolled up into this directory -->
<Copy SourceFiles="@(TerminalConnectionDlls)"

View File

@@ -15,6 +15,8 @@ Author(s):
#pragma once
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
@@ -33,12 +35,6 @@ Author(s):
#include <json.h>
#include "consoletaeftemplates.hpp"
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include "winrt/Windows.UI.Xaml.Markup.h"
#include <winrt/Windows.system.h>
@@ -58,8 +54,18 @@ Author(s):
#include <winrt/windows.applicationmodel.core.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <regex>
#include <CLI11/CLI11.hpp>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"

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