Compare commits

..

262 Commits

Author SHA1 Message Date
Dustin L. Howett
6b2659bf65 Migrate spelling-0.0.21 changes from main 2021-04-27 09:27:31 -07:00
Dustin L. Howett
466b80496d Migrate spelling-0.0.19 changes from main 2021-04-27 09:27:31 -07:00
Michael Niksa
af2712b09e saving what I have 2021-04-27 09:27:31 -07:00
Michael Niksa
d2372342e2 npos 2021-03-25 13:50:01 -07:00
Michael Niksa
e59f2b8477 make spellcheck happy 2021-03-25 12:26:20 -07:00
Michael Niksa
60ef914956 Handful of PR feedback. 2021-03-25 12:25:26 -07:00
Michael Niksa
cdc5a6a747 Merge branch 'main' into dev/miniksa/rle 2021-03-24 11:02:08 -07:00
Don-Vito
da24f7d939 Allow overriding tab switcher mode on command level (#9507)
## Summary of the Pull Request

Currently, when the MRU is enabled we lose the keybinding allowing us to 
go forward/backward (aka right/left in LTR) in the tab view.

To fix that, this PR introduces "tabSwitcherMode" optional parameter to 
the prevTab / nextTab commands.
If it is not provided the global setting will be used.


So if you want to go to adjacent tabs, even if MRU is enabled on the
system level you can use:
```
{ "command": { "action": "prevTab", "tabSwitcherMode": "inOrder" }, "keys": "ctrl+f1"}
{ "command": { "action": "nextTab", "tabSwitcherMode": "inOrder" }, "keys": "ctrl+f2"}
```
or even
```
{"command": { "action": "prevTab", "tabSwitcherMode": "disabled" }, "keys": "ctrl+f1"}
{ "command": { "action": "nextTab", "tabSwitcherMode": "disabled" }, "keys": "ctrl+f2"}
```
if you don't want tab switcher to show up

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9330
* [x] CLA signed. 
* [x] Tests added/passed
* [ ] Documentation updated - not yet. Waiting for approval.
* [x] Schema updated.
* [ ] I've discussed this with core contributors already.
2021-03-23 22:00:07 +00:00
Dustin L. Howett
da3f02a6e2 Command Palette: announce various mode and state changes to UIA (#9582)
This commit introduces a few different announcements to the command
palette.

When you delete the `>`, it will announce that you have entered
"command-line mode". When you reintroduce the `>`, it will announce that
you are in "action search mode."

When you enter a nested command, it will announce that you are looking
at "more options for new tab..." or "more options for select color
scheme...".

When you search and find nothing, it will announce that there were no
matching commands (or tabs!)

Related to #7907.
2021-03-23 08:24:27 -05:00
Dustin L. Howett
d972ea2c28 Replace conhost's fmt::format strings with FMT_COMPILEd ones (#9581)
This reduces by 10% the binary size of OpenConsole x64 Release.

Note   | OpenConsole.exe
------ | ---------------------------
Before | 1156096
After  | 1037312
Delta  | -118784
%Delta | -10.27%
2021-03-23 02:21:18 +00:00
Don-Vito
a5ff7459b7 Prevent tab context menu from closing root pane directly (#9571)
## Summary of the Pull Request
Currently a repeated attempt to close a read-only tab from context menu,
will bring the terminal into invalid state if user dismisses close action.

There are two root causes for this:
1. The tab close menu triggers the closing of the root pane
(rather than invoking close tab flow in the Terminal Page).
2. Currently panes are not aware that the closing was canceled,
and thus they trigger the Closed event, putting the system in a weird state,
where the Closed handlers were invoked, but the Pane remains.

This PR mitigates #9502, by addressing the first root cause
(the fix is trivial and hopefully can be serviced).
Moreover, it addresses the only existing UI flow that can trigger the issue.

The remaining problematic flow will occur when the connection is closed.
I have created a separate Issue to track it: 
https://github.com/microsoft/terminal/issues/9572
as I guess the PR for it might be more complex.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9502
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.
2021-03-22 20:48:18 +00:00
Dustin L. Howett
544bd44851 Update {fmt} to 7.1.3 (#9580)
{fmt} 7.1.3 includes a number of changes, which I will summarize here:

* Switched to Dragonbox for float formatting (quoted, "20-30x faster"
  than ostringstream)
* Significantly improves `FMT_COMPILE` (compile-time format string)
2021-03-22 13:07:04 -05:00
Don-Vito
9bd097f4ad Fix read-only tab dialog to cancel tab closing when dismissed (#9573)
Currently dismissing "are you sure you wish to close read-only tab or pane" 
dialog by pressing `ESC` will not abort tab closing
(aka the tab will be closed!)

The reason for this, is that we cancel, only if the "Cancel" is pressed
(aka result=PrimaryButton, while ESC returns result=None).

This PR fixes this, by doing what we usually do:
* Putting Cancel in the CloseButton (which is also triggered by ESC)
* Aborting the action if the result is not a Primary Button

However, since we want Cancel to be a default action,
we set CloseButton to be the DefaultButton  in XAML
2021-03-22 11:02:30 +00:00
Dustin L. Howett
9069fcab79 Remove the shell extension's dependency on fmt for huge savings (#9552)
Yeah, this one-line change shaves off almost half the binary.

Binary size savings (x64 Release):

Note   | WindowsTerminalShellExt.dll
------ | ---------------------------
Before | 130048
After  | 73728
Delta  | -56320
%Delta | -43.3%
2021-03-21 16:56:31 +00:00
Dustin L. Howett
f4d487efef github: migrate our bug report template to an issue form (#9538) 2021-03-19 12:03:43 -05:00
Mike Griese
fb734d166c Move events out of TermControl.h ; Use TYPED_EVENT in more places (#9526)
This is a small refactor on my way to much bigger, more painful refactors. This PR does five things:

* `TermControl.*` has historically had all the control-relevant EventArgs defined in the same file as TermControl. That's just added clutter to the files, clutter that could have been in it's own place.  We'll move all those event arg to their own files. 
* We'll also move `IDirectKeyListener` to its own file while we're at it.
* We'll update some of `TermControl`'s old `DEFINE`/`DECLARE_TYPED_EVENT` macros to the newer `TYPED_EVENT` macro, which is a bit nicer. 
* We'll change `TermControl.TitleChanged` to a typed event. I needed that for a future PR, so let's just do it here
* While we're updating `TYPED_EVENT` macros, let's do `TerminalPage` too.  

### checklist 
* [x] I work here
* [x] This is work for #1256, but we've got a long way to go before that works.
2021-03-18 22:02:39 +00:00
Mike Griese
2ed367fb49 Fix build break where Microsoft.Terminal.Control.dll is empty (#9537)
TIL that the `<None Include="Foo.def" />` line in our projects is
actually totally meaningless. The important line is the one that's in
`cppwinrt.build.pre.props`, where we declare 

```xml
<ModuleDefinitionFile Condition="Exists('$(ProjectName).def')">$(ProjectName).def</ModuleDefinitionFile>
```

So if you change a project's name, and not the `.def` file, then the
linker will just _not use the `.def` file at all_.

More importantly, this seemingly doesn't matter in debug builds. In a
Debug build, the linker will happily still include `WINRT_CanUnloadNow`
and `WINRT_GetActivationFactory` in the exports from the dll, even
without the `.def`. But in a Release build, the linker is much more
agressive about pruning symbols that aren't referenced, and without
those two, NONE of the symbols are eventually referenced.

This PR fixes `Microsoft.Terminal.Control` by renaming the `.def`, and
makes it marginally harder for someone to make the same mistake in the
future.

## References
* Regressed in #9472 

## PR Checklist
* [x] Closes #9529
* [x] I work here
2021-03-18 16:14:21 +00:00
Dustin L. Howett
acdcdcaccb Shell Extension: Remove C++/WinRT authoring dependency (#9525)
We don't need to use C++/WinRT's component authoring capabilities to be
a COM component. It's easier for us if we're not (and it makes the build
slightly faster!)

Binary size savings (x64 Release):

Note   | WindowsTerminalShellExt.dll
------ | ---------------------------
Before | 136192
After  | 130048
Delta  | 6144
%Delta | 4.5%
2021-03-17 21:52:32 +00:00
Mike Griese
d749df70ed Rename Microsoft.Terminal.TerminalControl to .Control; Split into dll & lib (#9472)
**BE NOT AFRAID**. I know that there's 107 files in this PR, but almost
all of it is just find/replacing `TerminalControl` with `Control`.

This is the start of the work to move TermControl into multiple pieces,
for #5000. The PR starts this work by:
* Splits `TerminalControl` into separate lib and dll projects. We'll
  want control tests in the future, and for that, we'll need a lib.
* Moves `ICoreSettings` back into the `Microsoft.Terminal.Core`
  namespace. We'll have other types in there soon too. 
  * I could not tell you why this works suddenly. New VS versions? New
    cppwinrt version? Maybe we're just better at dealing with mdmerge
    bugs these days.
* RENAMES  `Microsoft.Terminal.TerminalControl` to
  `Microsoft.Terminal.Control`. This touches pretty much every file in
  the sln. Sorry about that (not sorry). 

An upcoming PR will move much of the logic in TermControl into a new
`ControlCore` class that we'll add in `Microsoft.Terminal.Core`.
`ControlCore` will then be unittest-able in the
`UnitTests_TerminalCore`, which will help prevent regressions like #9455 

## Detailed Description of the Pull Request / Additional comments
You're really gonna want to clean the sln first, then merge this into
your branch, then rebuild. It's very likely that old winmds will get
left behind. If you see something like 

```
Error    MDM2007    Cannot create type
Microsoft.Terminal.TerminalControl.KeyModifiers in read-only metadata
file Microsoft.Terminal.TerminalControl.
```

then that's what happened to you.
2021-03-17 20:47:24 +00:00
Dustin L. Howett
a979e52f7c Teach OpenConsole.psm1 to support multiple terminal checkouts (#9509)
OpenConsole.psm1 cached its root directory on import, which made it very
difficult to move into another Terminal clone and _do things_ (like code
formatting).

By resolving the root directory again per-cmdlet, we gain the ability
to...

```powershell
cd terminal
import-module ./tools/openconsole.psm1
cd ../terminal-2
invoke-codeformat
```

... and have it format the correct directory (terminal-2) instead of the
incorrect one (terminal).

This does unfortunately break the openconsole cmdlets _inside dep/wil
and dep/gsl_ because they are separate git repositories. This is taken
as an acceptable cost.
2021-03-17 15:42:17 -05:00
Dustin L. Howett
1519236f2b Disambiguate the shell extension CLSIDs to allow usage SXS (#9510)
Fixes #6416
2021-03-17 20:32:45 +00:00
Mike Griese
43c469fc95 Add support for naming windows with the -w parameter (#9300)
This finishes the implementation of `--window` to also accept a string
as the "name" of the window. So you can say 

```sh
wt -w foo new-tab
wt -w foo split-pane
```

and have both those commands execute in the same window, the one named
"foo". This is just slightly more ergonomic than manually using the IDs
of windows. In the future, I'll be working on renaming windows, and
displaying these names. 

> #### `--window,-w <window-id>`
> Run these commands in the given Windows Terminal session. This enables opening
> new tabs, splits, etc. in already running Windows Terminal windows.
> * If `window-id` is `0`, run the given commands in _the current window_.
> * If `window-id` is a negative number, or the reserved name `new`, run the
>   commands in a _new_ Terminal window.
> * If `window-id` is the ID or name of an existing window, then run the
>   commandline in that window.
> * If `window-id` is _not_ the ID or name of an existing window, create a new
>   window. That window will be assigned the ID or name provided in the
>   commandline. The provided subcommands will be run in that new window.
> * If `window-id` is omitted, then obey the value of `windowingBehavior` when
>   determining which window to run the command in.

Before this PR, I think we didn't actually properly support assigning
the id with `wt -w 12345`. If `12345` didn't exist, it would make a new
window, but just assign it the next id, not assign it 12345.

## References
* #4472, #8135
* https://github.com/microsoft/terminal/projects/5

## Validation Steps Performed
Ran tests
Messed with naming windows, working as expected.

Closes https://github.com/microsoft/terminal/projects/5#card-51431478
2021-03-17 19:28:01 +00:00
Kayla Cinnamon
8346968881 Update roadmap to reflect new release cadence (#9366)
We're switching to a 6-week release cadence and are also shifting the 2.0 release to this winter.
2021-03-17 14:24:00 -05:00
Luan Vitor Simião Oliveira
c5124956a9 OpenConsole.psm1: use DevShell module; tidy up for PowerShell 6+ (#9508)
Saw this while snooping around, the gci command on PowerShell core seems
to store the full path in the variable as oppose to just the leaf.

The other modification is to use the PowerShell module that vs ships to
setup the environment.
2021-03-16 12:29:38 -05:00
Carlos Zamora
44f1ba6d4d Move TerminalSettings from TermApp to TerminalSettingsModel (#9318)
This accomplishes the first step towards embedding a preview on the Profiles/ColorSchemes page, by moving the `TerminalSettings` object over to the Terminal Settings Model project. We'll leverage this in a later PR to construct an embedded terminal in the settings UI.

`TerminalSettings` had to see a few more functions exposed in the IDL
(including some inheritance stuff).

Refresh the JSON to make TerminalSettings do it's thing across all the
open terminals.

References #9122 - Terminal Preview
References #6800 - SUI Epic
2021-03-15 23:15:25 +00:00
Geoff Lawrence
99b09c08d5 doc: add PowerShell install instructions added (#9474) 2021-03-15 12:29:20 -05:00
PankajBhojwani
fbdfc4d446 Check if found folder is not null when loading fragments (#9477)
When loading fragments, check if the public folder is not null first

Closes #9473
2021-03-15 16:34:30 +00:00
Don-Vito
28da6c4ecb Invalidate nested command with no valid subcommands (#9495)
Currently, when loading command with sub-commands that fail to parse,
we result with command that:
* Is not considered nested (has no sub-commands)
* Has no action of its own

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

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

Closes #9448
2021-03-15 16:34:06 +00:00
PankajBhojwani
b76087f561 Add a helper function to initialize TermControls in TerminalPage (#9296)
Since #8602 merged, we need to pass a child of the settings object to
the TermControl upon initializing it. Since this happens in a few places
in `TerminalPage`, its probably best to use a helper. 

Closes #9292
2021-03-12 20:08:10 +00:00
Don-Vito
efc92de19f Fix selection on multi-click with no shift (#9455)
When working on #9403 I completely forgot that
double-click and triple-click should work even without shift.
Fixed it by allowing multi-selection even if not shift is pressed.

Closes #9453

## Validation Steps Performed
* [x] single click = no selection
* [x] single click and drag = selection starting from first point
* [x] single click in unfocused pane and drag = focus pane, selection starting from first point
* [x] double-click = selects a whole word
* [x] triple-click = selects a whole line
* [x] double-click and drag = selects a whole word, drag selects whole words
* [x] triple-click and drag = selects a whole line, drag selects whole lines
* [x] Shift single-click = defines start point
* [x] second Shift single-click = defines end point
* [x] Shift double-click = selects entire word
* [x] Shift triple-click = selects entire line
* [x] Shift double-click and drag = selects entire word, drag selects whole words
* [x] Mouse mode: Shift single-click = defines start point
* [x] Mouse mode: second Shift single-click = defines end point
* [x] Mouse mode: Shift double-click = selects entire word
* [x] Mouse mode: Shift triple-click = selects entire line
* [x] Mouse mode: Shift double-click and drag = selects entire word, drag selects whole words
2021-03-12 03:37:57 +00:00
Dustin Howett
af896cd4bc Merge remote-tracking branch 'openconsole/inbox' into HEAD 2021-03-10 17:59:49 -06:00
Dustin Howett
1a1b4ff21e Merged PR 5777834: Parenthesis in incorrect spot for default app policy check.
Parenthesis in incorrect spot for default app policy check.

Related work items: MSFT-32071839

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev 537ab5dfd9cc6af60b570697e77bba4b45822e05
2021-03-10 23:59:12 +00:00
Dustin L. Howett
691e02ef1c Force DebugFull PDBs on all build types (#9441)
I've also taken the opportunity to kill the xxxFullPDB trick. The
intermediate PDB is allowed to remain "vc141.pdb" or whatever it wants
to be. PDBs are now simply named after their projects, as was always
tradition.
2021-03-10 23:49:03 +00:00
Dustin L. Howett
3029bb8a68 Update C++/WinRT to 2.0.210309.3 (#9437)
This update shrinks our binaries a little bit.

From a representative build on VS 16.8:

       | App     | Control | Connection | TSE     | WT     | Total   | msix (zip) |
       | --      | --      | --         | --      | --     | --      | --         |
Before | 2610176 | 1006592 | 433152     | 1352192 | 321536 | 5723648 | 8336910    |
After  | 2532352 | 986624  | 431104     | 1312768 | 313344 | 5576192 | 8287648    |
Delta  | 77824   | 19968   | 2048       | 39424   | 8192   | 147456  | 49262      |
%Delta | 2.98%   | 1.98%   | 0.47%      | 2.92%   | 2.55%  | 2.58%   | 0.59%      |
2021-03-10 16:04:59 -06:00
Dustin L. Howett
1e4c6978b7 submodule check: actually check for a real file in the repo (#9438)
Git may create folders for submodules; let's make sure they're populated.
2021-03-10 21:14:30 +00:00
Don-Vito
6cd4e03a58 Allow interaction with hyperlinks in mouse mode (#9396)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9117
* [x] CLA signed. 
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. 

## Detailed Description of the Pull Request / Additional comments
In mouse mode:
* Underline hyperlinks
* Activate hyperlink on ctrl+click rather than sending input to VT
2021-03-10 18:42:11 +00:00
PankajBhojwani
a47ed99272 Allow shift+click on a profile to open a new window (#9429)
<!-- 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
Shift+click on a profile to open a new wt window with that profile. Or, shift+click on the '+' button to open a new wt window with the default profile. 

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manual testing
2021-03-10 18:32:54 +00:00
Dustin Howett
95b031e27c ci: fix spelling issues from inbox merge 2021-03-09 17:46:43 -08:00
Dustin Howett
8f9ccc55d1 Merge inbox changes into main
This includes:
* The property sheet work for #492
* A fix for til::pmr on ARM inside Windows
2021-03-09 17:38:59 -08:00
Dustin Howett
3909cc103a Merged PR 5770253: [Git2Git] Merged PR 5760120: Add propsheet chooser to Windows
[Git2Git] Merged PR 5760120: Add propsheet chooser to Windows

Now the inbox console propsheet can choose which terminal is default

Related work items: MSFT-32007202 #492

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev d80f506858bd990c267de6cefae7ff55707b3a57
2021-03-10 01:19:04 +00:00
Eric Tian
8358f8d93f Display correct tooltip when window is maximized (#9412)
## Summary of the Pull Request
Instead of displaying "Maximize" in the tooltip for the maximize/restore button even when the window is maximized, it now displays "Restore Down".

## References
Fixes #5693

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

## Validation Steps Performed
Tested manually.
2021-03-09 17:41:29 +00:00
Eugene Samoylov
48d59e8304 [Settings UI] Represent Cursor Height as a slider (#9386)
Change the vintage cursor height number box to a slider.

## References
Related:  #9370

## PR Checklist
* [x] Closes #9377
* [x] zadjii-msft edit: Now _this one_ closes #9175
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Schema updated.
* [ ] 

## Detailed Description of the Pull Request / Additional comments

It seems like the cursor height couldn't be lower than 25 percent regardless of the given value, so I've changed the `MinCursorHeightPercent` in CustomTextRenderer header file.

## Validation Steps Performed
Manual validation

![CursorHeightSlider](https://user-images.githubusercontent.com/39456018/110041939-bf076080-7d66-11eb-8d58-ba9a84922803.gif)
2021-03-09 17:13:32 +00:00
Don-Vito
83f2a3bb3d Fix selection logic with shift on multi-click (#9403)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9382
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. 

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

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

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

This PR addresses the both defects in the following manner:
upon multi-click that starts new selection we establish
a new selection on every consequent click using appropriate mode
(cell/word/line) rather than trying to extend one.
For this purpose we remember the position that started the selection.
2021-03-08 19:15:46 +00:00
Carlos Zamora
87fa526fb7 Represent font face as a combo box in SUI (#9275)
## Summary of the Pull Request

This replaces the Profiles > Font Face text box with a combo box.

## References
#6800 - Settings UI Epic

## Detailed Description of the Pull Request / Additional comments

- Enumerating the fonts
  - [This doc](https://docs.microsoft.com/en-us/windows/win32/directwrite/font-enumeration) was the main reference used to enumerate the fonts. It was mildly adapted to use WinRT instead of WRL.
- Updating the UI
  - Similar to other combo box settings, `Profiles` keeps a reference to the current value. We use that as a way to update the settings model. If an invalid value is used, we fallback to `Cascadia Mono`.
  - A checkbox was added to let the user select from all of the installed fonts, or just the monospace ones.

## Demo
![Font Face Combo Box Demo](https://user-images.githubusercontent.com/11050425/109342917-6cd3b600-7821-11eb-8df9-fb988b037e02.gif)
2021-03-08 16:45:12 +00:00
Don-Vito
c6a31710d9 Allow configuring suppressApplicationTitle in new tab/pane/window commands (#9392)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9345
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated - not yet, will be once conceptually approved
* [x] Schema updated.
* [ ] I've discussed this with core contributors already. 

## Detailed Description of the Pull Request / Additional comments
Introduce optional `suppressApplicationTitle` in to `NewTerminalArgs`.
When set (either to true or false) overrides profile configuration.

Introduce `--suppressApplicationTitle` flag to command line arguments.
When provided for sub=command, 
sets the value in the relevant `NewTerminalArgs` to `true`
2021-03-08 15:23:50 +00:00
Don-Vito
19bd0c94e7 Fix TermControl initialization to pre-seed working dir (#9397)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/8969
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-03-08 14:56:36 +00:00
Don-Vito
629c06d0ad Introduce duplicate tab menu (#9388)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9373
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.
2021-03-08 12:16:56 +00:00
Don-Vito
1202f89399 Fix overflow in scroll-to-bottom (#9389)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9353
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.
2021-03-05 21:18:54 +00:00
Carlos Zamora
4a95341caf Change SettingsTab close icon to be smaller (#9324)
We were using the wrong close icon for the settings tab. This is a side effect of `TerminalTab` overriding `TabBase`'s implementation.

#6800 - Settings UI Epic

Closes #9317
2021-03-04 19:31:57 +00:00
Mike Griese
3cf7677d17 Replace some of our macros to reduce confusion, increase success (#9376)
As mentioned in https://github.com/microsoft/terminal/issues/9354#issuecomment-790034728

`GETSET_SETTING` is too visually similar to `GETSET_PROPERTY`, but with a _VERY_ different meaning. I think that merely changing the name of the macro would make it harder for us to make this mistake again.
2021-03-04 11:27:03 -08:00
Eugene Samoylov
ac3fecb134 [Settings UI] Clamp vintage cursor height and history size (#9370)
## Summary of the Pull Request
Add `Minimum` and `Maximum` for the cursor height numberbox in the SUI.
Add `Minimum` for the history size numberbox in the SUI.

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

## Validation Steps Performed
Manual validation
2021-03-04 17:47:23 +00:00
PankajBhojwani
5aaf5b4d59 Fix pixel shaders not loading (#9371)
<!-- 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
Fix for #9354 

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #9354 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [x] I work here
2021-03-04 17:19:21 +00:00
Carlos Zamora
2961a104af Add localization comments to settings UI (#9367)
We've received feedback from the localization team that the strings need some comments to provide more context.

## References
Closes [MSFT-31929817](https://microsoft.visualstudio.com/OS/_workitems/edit/31929817)
#9328 - Chinese translation issues
#9238 - French translation issues
2021-03-03 16:28:48 -08:00
Michael Niksa
7b1a660e59 Create settings/tasks definitions for VScode builds and registration (#9297)
I wanted to start using VScode. It wasn't easy. I wrote some tasks that allow us to build the various flavors of OpenConsole and Windows Terminal from one of the tasks. I also wrote a task that allows registration of the loose Windows Terminal package and a shortcut one to launch it. 

Also it was grinding away at its own Intellisense forever because it was indexing obj, bin, packages, etc. I excluded those.

Things should be easier now for folks in general. I expect we'll make more task types in the future.
2021-02-26 18:50:15 +00:00
Dustin L. Howett
cb03b97e67 Update Cascadia Code to 2102.25 (#9295)
This update fixes some issues in Cascadia Code's February update:

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

Full changelist:
* Repositioned tilde in related ligatures. Previously it was higher than the standard one.
* Added missing vietnamese anchors on acute and grave (futureproofing).
* Corrected / made consistent greater & less positioning in </> and <$> related ligatures.
* Otherwise reviewed hinting
2021-02-25 14:47:03 -08:00
PankajBhojwani
f26c246c7c Fix updating with fragments ignoring original profile settings (#9293)
Turns out we were adding the fragment source to profiles we update. This
PR fixes it so we keep the original source. 

## Validation Steps Performed
Existing profile settings are maintained

Closes #9290
2021-02-25 22:44:43 +00:00
Carlos Zamora
649c546960 Update dropdown's settings keyboard accelerator (#9294)
Fixes a bug introduced by #9224 where the wrong keyboard accelerator
would appear in the new tab dropdown. We were looking for the "settings
file" version of the action, as opposed to the "settings UI" version. 

## References
#9224 - Settings UI as default
#6800 - Settings UI Epic
2021-02-25 22:44:24 +00:00
PankajBhojwani
a930aa390e Fix settings not updating on reload (#9289)
In #8602, we started passing a child of the `TerminalSettings` to the
control upon tab initialization, but forgot to do the same when new
controls get created on a pane split. 

## Validation Steps Performed
Settings reload with multiple panes works

Closes #9280
2021-02-25 13:00:20 -08:00
Carlos Zamora
d1bf0fcd1e Add help text to 'more options' in command palette (#9271)
Similar to #9262. This creates another data template specifically for
command palette items that open up more options. We leverage the
localization key from #9262 to apply help text to this template
automatically.

Using the data template approach, we now have no need for the
`HasNestedComandsVisibilityConverter`, so that set of files is now
deleted. The logic to detect nested commands was moved to the template
selector.

## Validation Steps Performed
Tested using NVDA.

Addresses #7908 better
2021-02-24 12:02:09 -08:00
Dustin L. Howett
66033dcb01 Exclude MonarchPeasantPackage from AnyCPU/DotNet*Test configs (#9272)
Visual Studio automatically enabled this package to build in all
configurations. This results in a build error when we go to pack the WPF
control.
2021-02-24 11:34:34 -08:00
Dustin Howett
95f63a9100 [PICK] Cherry-pick loc key from 83d35c119 2021-02-23 18:57:57 -08:00
Dustin Howett
6654f0d155 version: bump to 1.4 on master 2021-02-23 16:35:25 -08:00
Carlos Zamora
35e1168bfa Make Settings UI the default experience (#9224)
This makes the settings UI the default settings experience.

As shown below, the following bindings are now default:
- <kbd>ctrl+,</kbd> --> settings ui
- <kbd>ctrl+shift+,</kbd> --> settings.json
- <kbd>ctrl+alt+,</kbd> --> defaults.json

The dropdown settings button aligns with this heuristic:
- click --> settings ui
- shift+click --> settings.json
- alt+click --> defaults.json
- if alt and shift both pressed, open settings.json

#6800 - Settings UI Epic
2021-02-24 00:14:13 +00:00
Carlos Zamora
ef4f2ca03e Update automation properties for tab header control (#9258)
This sets the automation property (name) on the tab view item we expose in XAML.

## PR Checklist
* [x] Closes #9254 

## Validation Steps Performed
Tested under NVDA and Accessibility Insights
2021-02-23 23:40:42 +00:00
Mike Griese
f87596f8f7 Add a simple page for keybindings (#9253)
This was the only thing blocking me from signing off on #9224 in 1.7.

! CHANGE WARNING !
If we bind to `T.S.M.Command`s in XAML, then the compiler gets _very
angry_ at us. It generates two different versions of
`GetReferenceTypeMember_Icon` in `XamlTypeInfo.g.cpp`. Presumably
because there's an Icon on a NavViewItem and an Icon on a Command. We
don't really know why. Fortunately, the fix is "rename Command::Icon" to
"Command::IconPath". It's dumb, but it works. Thanks for the help with
that one Carlos ☺️ 

Unblocks #9224
2021-02-23 23:37:23 +00:00
Dustin L. Howett
17c6f8e9ff TabHeader: put TraceLogging events in the right category (#9257)
Fixes MSFT-31615100
2021-02-23 15:14:04 -08:00
Dustin L. Howett
81d773d2bf PICK: Work around an optimizer issue with SetPixelShaderPath (#9249)
It appears as though the optimizer is generating a sequence of
instructions on x64 that results in a nonsense std::wstring_view being
passed to SetPixelShaderPath when it's converted from a winrt::hstring.

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

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

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

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

Closes #8723.
Closes #9064.

I found a repro (update the settings file every 0.5 seconds and resize
the terminal wildly while it's doing so) that would trigger the bug
within ~10 seconds. It stopped doing so.

(cherry picked from commit 91b867102c)
2021-02-22 13:01:51 -08:00
WVVxm
8ad4d1f19a Control 'Touch Keyboard and Handwriting Panel Service' warning (#9015)
Add a setting to turn off the warning if 'Touch Keyboard and
Handwriting Panel Service' is disabled.

The service might not start in some case, and it doesn't affect the
input in some computer.  This PR turn off the warning even if the
service is disabled.  The setting name is  "inputServiceWarning".

## Validation Steps Performed
I manually set the service to "Disabled", restarted the Terminal,
verified the warning up, then set "inputServiceWarning" to false and
restarted the Terminal, and the warning didn't appear.

References #8095
References https://github.com/microsoft/terminal/issues/7886#issuecomment-729350169
2021-02-22 12:08:49 -08:00
Mike Griese
99ffaa8a1a Fix wt --help (#9246)
When the user executes `--help`, make sure we force the creation of a new window, so that the `MessageBox` will actually appear. 

Add tests too. 

* [x] I work here
* [x] Fixes #9230
* [x] Tests added


I'm gonna have to immediately rewrite those tests for https://github.com/microsoft/terminal/projects/5#card-51431478, but this issue is ship-blocking so I don't care
2021-02-22 18:50:39 +00:00
Dustin L. Howett
c9dea60bbe Hide the Command Palette's key binding label from UIA (#9234)
The command palette's list items explicitly specify the "AcceleratorKey"
property (for UIA) and set it to the key binding. Putting it in a label
inside the list item makes Narrator read it out twice ("New Tab ...
ctrl+shift+t ... ctrl+shift+t").

Addresses #7913 (to be closed when a11y re-evaluates)
2021-02-22 18:30:08 +00:00
spiralofhope
a158cc81ae res: prepend XML line to SVG files, to pass strict svg checks (#9226)
A header line is missing from a few `.svg` files, denying their use (via
a security error) in WordPress (if svg support is enabled). The upload
is rejected even though the web browser could display the image if it
were dragged into its own tab.

The `.svg` images appear to have been edited at different times and with
different tools.  Their contents are in a different style.  Two of them
are beautiful and the rest do not follow suit and do not function the
same.

I don't know enough to make them all the same style, but changes can be
made to three of them to make them work the way I was expecting (see
below).

## Validation Steps Performed
- Perform the change with a text editor
- Open a new WordPress post page
- Drag-and-drop the changed file into that WordPress edit box
- (The WordPress media upload dialog appears and the file is uploaded)
- Confirmed that the file does not trigger a "security error" (as seen
  at the top of the right-hand column)
- Confirmed that the image appears as a thumbnail preview

`Terminal_Pre_HC.svg` is not fixable in this way, and I don't understand
svg well enough to troubleshoot easily.
2021-02-19 17:15:04 -08:00
Mike Griese
049e37e514 Add support for the newWindow action (#9208)
Finally implements the `newWindow` action. It does so by
`ShellExecute`ing `wt.exe` with commandline args corresponding to the
ones that would create the same `NewTerminalArgs`. This works with #8898
and #9118 to allow new windows (even with `windowingBehavior:
useExisting`)

This is taken from my auto-elevate branch, hence the references to
elevation

References #5000
References projects/5
References #8898
References #9118
Closes #1051
2021-02-19 23:51:30 +00:00
Carlos Zamora
eb0fb3e822 Introduce setting override tracking and update SettingContainer (#9079)
This PR adds improved override message generation for inheritance in
SUI. The settings model now has an `OriginTag` to be able to denote
where a `Profile` came from. This tag is used in the `SettingContainer`
to generate a more specific override message.

## References
#6800 - SUI Epic
#8919 - SUI Inheritance PR
#8804 - SUI Inheritance (old issue)

## Detailed Description of the Pull Request / Additional comments
- **Terminal Settings Model**
  - Introduced `PROJECTED_SETTING` as a macro to more easily declare the
    functions for each setting
  - Introduced `<setting>OverrideSource` which finds the `Profile` that
    has \<setting\> defined
  - Introduced `OriginTag Profile::Origin {Custom, InBox, Generated}` to
    trace where a profile came from
  - `DefaultProfileUtils` creates profiles for profile generators. So
    that now sets the `Origin` tag to `Generated`
  - `CascadiaSettings::LoadDefaults()` tags all profiles created as
    `InBox`.
  - The view model had to ingest the API change to be able to interact
    with `<setting>OverrideSource`
- **Override Message Generation**
  - The reset button now has a more specific tooltip
  - The reset button now only appears if base layer is being overridden
  - We use the settings model changes to determine the message to
    display for the target

## Validation Steps Performed
Tested the following cases:
- overrides nothing (inherited setting)
- overrides value inherited from...
  - base layer
  - a profile generator
  - in-box profile
- global settings should not have this feature
2021-02-19 23:50:52 +00:00
PankajBhojwani
dfeb855d18 Support the "file" URI scheme (#7526)
We now support the file URI schemes where the hostname is either
"localhost" or the empty string

References #5001

Fixes #7699
2021-02-19 14:32:54 -08:00
Mike Griese
ba8bd006f4 Add centerOnLaunch setting (#9036)
This PR is a resurrection of #8414. @Hegunumo has apparently deleted
their account, but the contribution was still valuable. I'm just here to
get it across the finish line.

This PR adds new global setting `centerOnLaunch`. When set to `true`,
the Terminal window will be centered on the display it opens on. 

So the interactions are like:

* `initialPos: x,y`, `centered: true`, `launchMode: default`
  center on the monitor that x,y is on 

* `initialPos: x,y`, `centered: true`, `launchMode: maximized`
  maximized on the monitor that x,y is on (centered adds nothing)

* `initialPos: <omitted>`, `centered: true`, `launchMode: default`
  center on the default monitor

* `initialPos: <omitted>`, `centered: true`, `launchMode: focus`
  center, focus mode on the default monitor

* `initialPos: <omitted>`, `centered: true`, `launchMode: maximized`
  maximized on the default monitor (centered adds nothing)

## Validation Steps Performed
I've played with it on multiple different monitors, and it seems to work
on all of them.

Closes #8414 (original PR)
Closes #7722 

Co-authored-by: Kiminori Kaburagi <yukawa_hidenori@icloud.com>
2021-02-19 22:30:24 +00:00
Carlos Zamora
177430272c Polish settings UI localized text (#9124)
Updates the following text in the settings UI
- focus follow mouse mode is introduced to be more instructional
- focus follow mouse mode tooltip removed
- avoid double negative in "disable pane animation"

Closes #8900 
Updates #6459 Settings UI text
2021-02-19 14:27:30 -08:00
Mike Griese
69318d3ba1 Add support for the windowingBehavior setting (#9118)
Adds support for the `windowingBehavior` global setting. This setting
controls how mutiple instances of `wt` behave in the absence of the `-w`
parameter. This setting has three values:
* `"useNew"`: (default) Multiple `wt` invocations (without the `-w`
  param) always create new windows. 
* `"useAnyExisting"`: When starting a new `wt`, we'll instead default to
  any existing windows. `wt -w -1` will still create new windows. 
* `"useExisting"`: Similar to `useAnyExisting`, but limits to
  windows on the current desktop. 

The IVirtualDesktopManager interface is _very_ limited. Hence why we
have to track the HWNDs manually, and ask if they're on the current
desktop. 

## Validation Steps Performed
I've been playing with it for a week now. 

References #5000
References projects/5
References #8898
Spec'd in #8135
Closes #2227
Closes https://github.com/microsoft/terminal/projects/5#card-51431448
Closes https://github.com/microsoft/terminal/projects/5#card-51431433
2021-02-19 21:09:17 +00:00
Dustin Howett
ce99c2a349 Merged PR 5704731: Migrate OSS up to e6aa90222
Related work items: MSFT-9667854, MSFT-31783834
2021-02-19 19:45:33 +00:00
Don-Vito
e6aa902224 Fix FilteredCommand tests to run on UI thread (#9209)
Fixes exception in FilteredCommandTests, caused due to
an attempt to instantiate PropertyChangedEventArgs on non-UI thread.

Though the diff might look scary it is just wrapping with `RunOnUIThread`
2021-02-19 18:22:09 +00:00
PankajBhojwani
d12a71cdf9 Always show the bell indicator when BEL is emitted (#9212)
Regardless of whether the bellstyle is set to visual,
show the bell indicator in the tab header
2021-02-19 18:21:35 +00:00
Carlos Zamora
bb0e1d3979 Redesign color schemes page (#9196)
This PR performs a large overall polish of the color schemes page:
- Ensures keyboard navigation is holistically improved (i.e. fully
  accessible, no lost focus, etc...)
- Adds tooltips and automation properties to all controls
- Redesigns the page according to @mdtauk's approved design
  ([link](https://github.com/microsoft/terminal/pull/8997#issuecomment-771623842)).
  Note, there are some minor modifications to the design that were
  approved by @cinnamon-msft.
- Automatically reflow's the color buttons when they do not fit in
  horizontal mode

## Detailed Description of the Pull Request / Additional comments
- Redesign
  - a data template was introduced to make color representation
    consistent and straightforward
  - `ContentControl` is used to hold a reference to the
    `ColorTableEntry` and represent it properly using the aforementioned
    data template.
  - The design is mainly a StackPanel holding two grids: color table &
    functional colors.
  - The color table is populated via code. After much thought, this
    seems to be the easiest way to correctly bind 16 controls that are
    very similar.
  - The functional colors are populated via XAML manually.
  - We need a grid to separate the text and the buttons. This allows for
    scenarios like "selection background is the longest string" to force
    the buttons and text to be aligned.
- Reflow
  - A `VisualStateManager` uses an `AdaptiveTrigger` to change the
    orientation of the color tables' stack panel. The adaptive trigger
    was carefully calculated to align with the navigation view's
    breakpoint.
- Keyboard Navigation
  - (a lesson from `SettingContainer`) `ContentControl` can be focused
    as opposed to the content inside of it. We don't want that, so we
    set `IsTabStop` to false on it. That basically fixes all of our
    keyboard navigation issues in this new design.
- Automation Properties and ToolTips
  - As in my previous PRs, I can't seem to figure out how to bind to a
    control's automation property to its own tooltip. So I had to do
    this in the code and add a few `x:Name` around.

## Validation Steps Performed
- Manually tested...
  - tab navigation
  - accessibility insights
  - NVDA
  - changing color schemes updates color table
- specific scenario:
  - change a color table color and a functional color
  - navigate to a different color scheme
  - navigate back to the first color scheme
  - if the colors persist, the changes were propagated to the settings model

References #8997 - Based on the work from @Chips1234
References #6800 - Settings UI Epic
Closes #8765 - Polish Color Schemes page
Closes #8899 - Automation Properties
Closes #8768 - Keyboard Navigation
2021-02-19 18:20:04 +00:00
Carlos Zamora
2c22b68e15 Enable text search on combo boxes (#9206)
`ComboBox` has a text search function that allows users to type letters, and the `ComboBoxItem` starting with those letters is shown. In order to enable this functionality, the underlying items must be `IStringable`. This exposes a `ToString()` function and fixes all of our issues.

This PR adds the `IStringable` interface to `ColorScheme`, `Profile`, and `EnumEntry`.

## References
#6800 - Settings UI Epic
#8768 - Keyboard Navigation
https://github.com/microsoft/microsoft-ui-xaml/issues/4182 - discussion with WinUI about how to overcome this issue

## Validation Steps Performed
Tested...
- Launch > Default Profile
- Color Schemes > Name
- Profile > Appearance > Color scheme
- Profile > Appearance > Font weight

Also tested radio buttons, but those still don't work, unfortunately. Looks like they don't have the same underlying mechanism.
2021-02-19 18:11:07 +00:00
PankajBhojwani
654c0cc286 Add support for "fragment extensions" (#7632)
Support for fragment extensions, according to the implementation
outlined in #7584 (which calls them proto extensions.)

See #7584 for more information.

## Validation Steps Performed
Self-testing by creating the folder 
`%LOCALAPPDATA%\Microsoft\Windows Terminal\Fragments`
and adding a json file into it to modify and add profiles

Also self-tested with an app extension

Closes #1690
2021-02-19 02:12:16 +00:00
Mike Griese
c07553cb57 A bunch of test fixes (#9192)
A bunch of our local tests regressed recently. I'm unsure as to when
this happened. Clearly, we all do a super good job of running these
tests 😄.
* I had to make sure the call to `AppLogic::CurrentAppSettings` was
  try/caught, because that doesn't work in the tests
* I had to make the `Pointer*` events take a weak pointer to the
  `TerminalPage` because for whatever reason, they'd be called at a
  weird point in the test init, causing the tests to fail. It was weird.
  Almost as if the TerminalPage had been released, but the test logs
  showed it hadn't barely been set up yet? Whatever, this fixes it.
* The `VerifyCommandPaletteTabSwitcherOrder` test needed to take a time
  out, for reasons that are not totally clear to me. That one was flakey
  and I hate it.

### Checklist:
* [x] Doesn't close anything, this is just something I noticed.
* [x] Doesn't require docs to be updated, it's test fixes
* [x] Yea, I ran the tests 

/cc @Don-Vito: The `FilteredCommandTests` all crashed immediately for
me. I'm not sure what's causing that - I _think_ everything we need for
those tests is set up right? The generated `AppxManifest.xml` had all
the right classes listed in it, I really can't be sure what was wrong
there. These tests aren't run in CI so it's not a super big deal, but I
thought I'd let you know.

(cherry picked from commit ccda434f69)
2021-02-18 20:47:14 +00:00
Mike Griese
491cb21722 Add findNext, findPrev actions (#8917)
This PR is a resurrection of #8522. @Hegunumo has apparently deleted
their account, but the contribution was still valuable. I'm just here to
get it across the finish line.

This PR adds new action for navigating to the next & previous search
results. These actions are unbound by default. These actions can be used
from directly within the search dialog also, to immediately navigate the
results. 

Furthermore, if you have a search started, and close the search box,
then press this keybinding, _it will still perform the search_. So you
can just hit <kbd>F3</kbd> repeatedly with the dialog closed to keep
searching new results. Neat!

If you dispatch the action on the key down, then dismiss a selection on
a key up, we'll end up immediately destroying the selection when you
release the bound key. That's annoying. It also bothers @carlos-zamora
in #3758. However, I _think_ we can just only dismiss the selection on a
key up. I _think_ that's fine. It _seems_ fine so far. We've got an
entire release cycle to futz with it.

## Validation Steps Performed
I've played with it all day and it seems _crisp_.

Closes #7695 

Co-authored-by: Kiminori Kaburagi <yukawa_hidenori@icloud.com>
2021-02-18 19:21:35 +00:00
Don-Vito
90699da23b Fix FFM to apply only if tab is focused (#9198)
In the FFM mode, hovering on the pane might dismiss renamer.
To address this we want to make sure that FFM is applied
only if the Terminal Tab is focused.
2021-02-18 17:15:44 +00:00
Chester Liu
eb349935a0 Introduce DxFontRenderData (#9096)
This is my attempt to isolate all the dwrite font related thing by
introducing a new layer - `DxFontRenderData`. This will free
`DxRenderer` & `CustomTextLayout` from the burden of handling fonts &
box effects. The logic is more simplified & streamlined.

In short I just moved everything fonts-related into `DxFontRenderData`
and started from there. There's no modification to code logic. Just pure
structural stuff.

SGR support tracking issue: #6879
Initial Italic support PR: #8580
2021-02-18 06:11:38 +00:00
James Holderness
4c53c595e7 Add support for double-width/double-height lines in conhost (#8664)
This PR adds support for the VT line rendition attributes, which allow
for double-width and double-height line renditions. These renditions are
enabled with the `DECDWL` (double-width line) and `DECDHL`
(double-height line) escape sequences. Both reset to the default
rendition with the `DECSWL` (single-width line) escape sequence. For now
this functionality is only supported by the GDI renderer in conhost.

There are a lot of changes, so this is just a general overview of the
main areas affected.

Previously it was safe to assume that the screen had a fixed width, at
least for a given point in time. But now we need to deal with the
possibility of different lines have different widths, so all the
functions that are constrained by the right border (text wrapping,
cursor movement operations, and sequences like `EL` and `ICH`) now need
to lookup the width of the active line in order to behave correctly.

Similarly it used to be safe to assume that buffer and screen
coordinates were the same thing, but that is no longer true. Lots of
places now need to translate back and forth between coordinate systems
dependent on the line rendition. This includes clipboard handling, the
conhost color selection and search, accessibility location tracking and
screen reading, IME editor positioning, "snapping" the viewport, and of
course all the rendering calculations.

For the rendering itself, I've had to introduce a new
`PrepareLineTransform` method that the render engines can use to setup
the necessary transform matrix for a given line rendition. This is also
now used to handle the horizontal viewport offset, since that could no
longer be achieved just by changing the target coordinates (on a double
width line, the viewport offset may be halfway through a character).

I've also had to change the renderer's existing `InvalidateCursor`
method to take a `SMALL_RECT` rather than a `COORD`, to allow for the
cursor being a variable width. Technically this was already a problem,
because the cursor could occupy two screen cells when over a
double-width character, but now it can be anything between one and four
screen cells (e.g. a double-width character on the double-width line).

In terms of architectural changes, there is now a new `lineRendition`
field in the `ROW` class that keeps track of the line rendition for each
row, and several new methods in the `ROW` and `TextBuffer` classes for
manipulating that state. This includes a few helper methods for handling
the various issues discussed above, e.g. position clamping and
translating between coordinate systems.

## Validation Steps Performed

I've manually confirmed all the double-width and double-height tests in
_Vttest_ are now working as expected, and the _VT100 Torture Test_ now
renders correctly (at least the line rendition aspects). I've also got
my own test scripts that check many of the line rendition boundary cases
and have confirmed that those are now passing.

I've manually tested as many areas of the conhost UI that I could think
of, that might be affected by line rendition, including things like
searching, selection, copying, and color highlighting. For
accessibility, I've confirmed that the _Magnifier_ and _Narrator_
correctly handle double-width lines. And I've also tested the Japanese
IME, which while not perfect, is at least useable.

Closes #7865
2021-02-18 05:44:50 +00:00
Dan Thompson
72cbe59078 Add support for XTPUSHSGR / XTPOPSGR (#1978)
Implement the `XTPUSHSGR` and `XTPOPSGR` control sequences (see #1796).

This change adds a new pair of methods to `ITermDispatch`:
`PushGraphicsRendition` and `PopGraphicsRendition`, and then plumbs the
change through `AdaptDispatch`, `TerminalDispatch`, `ITerminalApi` and
`TerminalApi`.

The stack logic is encapsulated in the `SgrStack` class, to allow it to
be reused between the two APIs (`AdaptDispatch` and `TerminalDispatch`).

Like xterm, only ten levels of nesting are supported.

The stack is implemented as a "ring stack": if you push when the stack
is full, the bottom of the stack will be dropped to make room.

Partial pushes (see the description of `XTPUSHSGR` in Issue #1796) are
implemented per xterm spec.

## Validation Steps Performed
Tests added, plus manual verification of the feature.

Closes #1796
2021-02-17 18:31:52 -08:00
Dustin Howett
ce0505073c Merged PR 5695814: Migrate OSS up to 847749f19
Dustin L. Howett (3)
* Fix the Host Proxy DLL reference in ServerLib (GH-9129)
* ci: fix spelling
* Format the incoming inbox code

Michael Niksa (1)
* Eliminate more transient allocations: Titles and invalid rectangles and bitmap runs and utf8 conversions (GH-8621)

Related work items: MSFT-31755835
2021-02-17 22:04:25 +00:00
Mike Griese
847749f19e Force activate existing windows when running a commandline in them (#9137)
This will make sure to summon the terminal window when running a
commandline in it. 
* If the window is on another desktop, the OS will switch to the desktop
  the window is on. 
* If the window is minimized, it will restore it.

This is taken from my quake mode branch. It works aggressively. 848682a,
fee6473, 342d3f2, 5052d31 all had other attempts at doing this, but they
didn't work reliably. Part of the trick is that I don't _think_ Windows
wants one process to be able to move another process into the
foreground. In this case though, we _do_ want to move ourself into the
foreground, and this `AttachThreadInput` hack seems to be the only way
to do it reliably.

References #5000
Uses code authored for #653

Closes https://github.com/microsoft/terminal/projects/5#card-54636373
2021-02-17 21:37:22 +00:00
Don-Vito
52d1533c4f Ensure hyperlinks de-underline when pointer leaves terminal (#9195)
Introduced PointerExited handler to nullify last hovered cell
(which will also de-underline the hyperlink if required).

Closes #9141
2021-02-17 13:33:53 -08:00
Carlos Zamora
ac3e4bfe56 Fix command palette accessibility (#9143)
Fixes a regression of command palette accessibility. The regression was
introduced in #8377 by setting `IsTabStop` to false. Though the commands
would light up, the focus didn't technically get on the command, so the
screen reader would just read the text box.

## Validation Steps Performed
Opened the command palette while NVDA is active. It now reads the
commands as focus moves on them.
2021-02-17 21:30:45 +00:00
Javier
12eb69f665 wpf: prevent a 0 size when VS builds the Terminal window too early (#9194)
The terminal WPF container might have a (0,0) render size when VS
eagerly attempts to initialize the terminal tool window when it is
pinned during startup, but no actual UI is shown due to the VS welcome
dialog that shows up before VS can build the terminal tool window.

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

This should patch all the gaps that cause this bug.

## Validation Steps Performed
I've manually validated the scenarios shown in the user dumps.
2021-02-17 21:00:50 +00:00
Don-Vito
00d1dc99e4 Teach tab to focus terminal after rename (#9162)
## Summary of the Pull Request
After rename ends (either by enter or escape) the rename box
gets collapsed and the focus moves to the next tab stop
in the TabView (e.g., new tab button).

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

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

## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9160
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.
2021-02-17 20:00:23 +00:00
Carlos Zamora
557edc629d Perform polish of miscellaneous Settings UI bugs (#9126)
- 0b0dbdf Makes the browse buttons center vertically aligned
  - This is now made possible by #8919. The "center" used to include the height of the header. Now that it's separated, the center is solely calculated to be the text box.
  - Closes #8764 
- 0288f06 Fix keyboard navigation focus for color schemes rename button
  - Enter/Esc when in the scheme renamer now focuses the combo box
  - Keyboard-invoking accept/cancel button focuses the rename button
  - References #8765 and #8768
- d5ef552 Cyclical tab navigation
  - now, if you try to tab past the save button, you cycle back to the beginning of the navigation view
  - this is consistent with the xaml controls gallery
  - References #8768 
- a613b08 AutomationProperties for Save, Reset, and open json buttons
  - References #8899
2021-02-17 17:43:47 +00:00
SJ
7d37ba22e7 Do not dismiss selection if the Windows keys is pressed as a key-combination (#9163)
Aims to fix #8791.

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
1. Build Terminal.
2. Write anything & make a selection.
3. Press `Windows`+ `Shift` + `S` keys.
4. The _Capture & Annotate_ tool appears but the selection made in step 2 isn't dismissed (doesn't disappear).
2021-02-17 17:41:25 +00:00
Michael Niksa
525be22bd8 Eliminate more transient allocations: Titles and invalid rectangles and bitmap runs and utf8 conversions (#8621)
## References
* See also #8617 

## PR Checklist
* [x] Supports #3075
* [x] I work here.
* [x] Manual test.

## Detailed Description of the Pull Request / Additional comments

### Window Title Generation
Every time the renderer checks the title, it's doing two bad things that
I've fixed:
1. It's assembling the prefix to the full title doing a concatenation.
   No one ever gets just the prefix ever after it is set besides the
   concat. So instead of storing prefix and the title, I store the
   assembled prefix + title and the bare title.
2. A copy must be made because it was returning `std::wstring` instead
   of `std::wstring&`. Now it returns the ref.

### Dirty Area Return
Every time the renderer checks the dirty area, which is sometimes
multiple times per pass (regular text printing, again for selection,
etc.), a vector is created off the heap to return the rectangles. The
consumers only ever iterate this data. Now we return a span over a
rectangle or rectangles that the engine must store itself.
1. For some renderers, it's always a constant 1 element. They update
   that 1 element when dirty is queried and return it in the span with a
   span size of 1.
2. For other renderers with more complex behavior, they're already
   holding a cached vector of rectangles. Now it's effectively giving
   out the ref to those in the span for iteration.

### Bitmap Runs
The `til::bitmap` used a `std::optional<std::vector<til::rectangle>>`
inside itself to cache its runs and would clear the optional when the
runs became invalidated. Unfortunately doing `.reset()` to clear the
optional will destroy the underlying vector and have it release its
memory. We know it's about to get reallocated again, so we're just going
to make it a `std::pmr::vector` and give it a memory pool. 

The alternative solution here was to use a `bool` and
`std::vector<til::rectangle>` and just flag when the vector was invalid,
but that was honestly more code changes and I love excuses to try out
PMR now.

Also, instead of returning the ref to the vector... I'm just returning a
span now. Everyone just iterates it anyway, may as well not share the
implementation detail.

### UTF-8 conversions
When testing with Terminal and looking at the `conhost.exe`'s PTY
renderer, it spends a TON of allocation time on converting all the
UTF-16 stuff inside to UTF-8 before it sends it out the PTY. This was
because `ConvertToA` was allocating a string inside itself and returning
it just to have it freed after printing and looping back around again...
as a PTY does.

The change here is to use `til::u16u8` that accepts a buffer out
parameter so the caller can just hold onto it.

## Validation Steps Performed
- [x] `big.txt` in conhost.exe (GDI renderer)
- [x] `big.txt` in Terminal (DX, PTY renderer)
- [x] Ensure WDDM and BGFX build under Razzle with this change.
2021-02-16 20:52:33 +00:00
Ben Constable
ca226d62e2 Add Keyboard Navigation To Color Picker (#9144)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

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

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

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

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

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

Tested manually
2021-02-16 17:36:34 +00:00
Dustin L. Howett
c1f844307c Remove accidental System.Core.dll from CascadiaPackage (#9153)
A bug in VS 16.8 makes the WAP packaging project copy System.Core.dll
from the CLR into all WAP packages. We don't need it, and it adds 300kb
to our package (670kb uncompressed).

VS 16.9 sets the AddAdditionalExplicitAssemblyReferences to suppress
this assembly. If we do the same, we can avoid the reference *and* be
eady for VS 16.9.
2021-02-16 12:01:14 +00:00
Don-Vito
5ffb945b66 Re-enable navigation in ATS (#9140)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/9130
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.
2021-02-13 04:00:13 +00:00
Dustin L. Howett
604eaf9bdd Fix the Host Proxy DLL reference in ServerLib (#9129)
Fixes #9128
2021-02-12 18:54:23 +00:00
Dustin Howett
13e058d9f1 ci: fix spelling 2021-02-11 14:08:37 -08:00
Dustin Howett
2f28d24fe0 Format the incoming inbox code 2021-02-11 13:19:27 -08:00
Dustin Howett
49667c263f Merge remote-tracking branch 'openconsole/inbox' into HEAD 2021-02-11 13:18:25 -08:00
Dustin Howett
3822d5b662 Merged PR 5677497: [Git2Git] Merged PR 5655213: Allow conhost to handoff to registered default app handler
Contains:
- Delegation Configurator that can lookup/edit/save configuration information to registry
- Conhost can lookup the CLSID of a registered default
- Conhost has the ability to handoff a starting visible-window interactive session to the registered default
- Velocity key since this is a big deal and we want to be careful
- IDL for the interface

Related work items: MSFT-16458099
Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev 0ca55027d8180fbbaa145f2fe7a15005856c0f7c
2021-02-11 21:07:50 +00:00
Dustin Howett
7fac0c3571 ci: add dataobject to the dictionary 2021-02-11 11:52:22 -08:00
Dustin Howett
7e11aeca0a ci: add reimplementation to the dictionary 2021-02-11 11:47:51 -08:00
Dustin Howett
38da2ff185 Merged PR 5676764: Migrate OSS up to 16d00a68f
Dustin L. Howett (3)
* Move CharToKeyEvents (and friends) into InteractivityBase (GH-9106)
* Update Cascadia Code to 2102.03 (GH-9088)
* verison: bump to 1.7 on main

Josh Soref (1)
* ci: update to Spell check to 0.0.17a (CC-9014)

Leonard Hecker (3)
* Fixed GH-5205: Ctrl+Alt+2 doesn't send ^[^@ (CC-5272)
* Fix issues in tests.xml and OpenConsole.psm1 (CC-9011)
* Fix GH-8458: Handle all Ctrl-key combinations (CC-8870)

Mike Griese (1)
* Add support for running a commandline in another WT window (GH-8898)

Michael Niksa (1)
* Teach the renderer to keep thread alive if engine requests it (GH-9091)

Lachlan Picking (1)
* Fix shader time input (CC-8994)

PankajBhojwani (1)
* Separate runtime TerminalSettings from profile-TerminalSettings (CC-8602)

Chester Liu (2)
* Add support for paste filtering and bracketed paste mode (CC-9034)
* Add support for chaining OSC 10-12 (CC-8999)

Related work items: MSFT-31692939
2021-02-11 18:38:37 +00:00
Dustin Howett
c3e0a8dce5 Merged PR 5676675: Reflect OS build fixes on top of ae8347f33
Due to a minor OS issue, we needed to shim PMR _even more_.

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev a83daa5e2af50121d79be082d005ff2e969ad18e

Related work items: MSFT-31478342
2021-02-11 18:19:33 +00:00
Don-Vito
16d00a68fe Prevent read-only warning on mouse move, wheel and release (#9107)
Avoid sending mouse move / wheel / release to Terminal in the first place.
This kind of short-circuiting will prevent us reaching the
attempt to send input to connection
(that could result in the warning dialog in the read-only mode)

Closes #9074
2021-02-11 15:16:36 +00:00
Dustin L. Howett
4440256eba PICK: Move CharToKeyEvents into InteractivityBase (GH-9106)
These functions have a dependency on the "VT Redirected" versions of
VkKeyScanW, MapVirtualKeyW and GetKeyState. Those implementations depend
on the service locator and therefore the entire interactivity stack.

This meant that anybody depending on just Types had to pull in **the
entire host** worth of dependencies (!).

Since these functions are only used in places where we have or are
testing interactivity, it makes sense to consolidate them here.

(cherry picked from commit 8f73145d9d)
2021-02-10 17:13:00 -08:00
Dustin L. Howett
8f73145d9d Move CharToKeyEvents (and friends) into InteractivityBase (#9106)
These functions have a dependency on the "VT Redirected" versions of
VkKeyScanW, MapVirtualKeyW and GetKeyState. Those implementations depend
on the service locator and therefore the entire interactivity stack.

This meant that anybody depending on just Types had to pull in **the
entire host** worth of dependencies (!).

Since these functions are only used in places where we have or are
testing interactivity, it makes sense to consolidate them here.
2021-02-10 17:10:56 -08:00
Carlos Zamora
42511265e5 Bugfix: update color scheme references properly (#9103)
`CascadiaSettings::UpdateColorSchemeReferences` had two bugs in it:
1. we would never check/update the base layer
2. we would explicitly set the color scheme on a profile referencing the
   old name

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

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

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

Closes #9094 

[DHowett's scenario]: https://github.com/microsoft/terminal/issues/9094#issuecomment-776412781
2021-02-10 22:11:56 +00:00
Don-Vito
ed19301ad3 Fix CommandPalette to prefer inner interactions over bindings (#9056)
* Currently TerminalPage registers on CmdPal key events:
  * To invoke bindings when the palette is open
  * Since some key combinations are not triggered by KeyDown 
    it registers for PreviewKeyDown
* As a result bindings might be preferred over navigation
  (e.g., ctrl+v will paste into Terminal rather than into search box)
* To fix this, I moved all interactions inside the CmdPal into
  PreviewKeyDown as well
* In addition, added specific handling for copy/paste
  which now allow to interact with search box even if not focused

Closes #9044
2021-02-10 12:35:46 -08:00
Michael Niksa
3b247812ce Teach the renderer to keep thread alive if engine requests it (#9091)
Teaches renderer base to keep thread alive if engine requests it.
`DxEngine` now requests it if shaders are on.

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

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

I promised this in #8994
2021-02-10 11:24:45 -08:00
Mike Griese
03ebe514e9 Add support for running a commandline in another WT window (#8898)
## Summary of the Pull Request

**If you're reading this PR and haven't signed off on #8135, go there first.**

![window-management-000](https://user-images.githubusercontent.com/18356694/103932910-25199380-50e8-11eb-97e3-594a31da62d2.gif)

This provides the basic parts of the implementation of #4472. Namely:
* We add support for the `--window,-w <window-id>` argument to `wt.exe`, to allow a commandline to be given to another window.
    * If `window-id` is `0`, run the given commands in _the current window_.
    * If `window-id` is a negative number, run the commands in a _new_ Terminal window.
    * If `window-id` is the ID of an existing window, then run the commandline in that window.
    * If `window-id` is _not_ the ID of an existing window, create a new window. That window will be assigned the ID provided in the commandline. The provided subcommands will be run in that new window.
    * If `window-id` is omitted, then create a new window.


## References
* Spec: #8135
* Megathread: #5000
* Project: projects/5

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

## Detailed Description of the Pull Request / Additional comments

Note that `wt -w 1 -d c:\foo cmd.exe` does work, by causing window 1 to change 

There are limitations, and there are plenty of things to work on in the future:
* [ ] We don't support names for windows yet
* [ ] We don't support window glomming by default, or a setting to configure what happens when `-w` is omitted. I thought it best to lay the groundwork first, then come back to that.
* [ ] `-w 0` currently just uses the "last activated" window, not "the current". There's more follow-up work to try and smartly find the actual window we're being called from.
* [ ] Basically anything else that's listed in projects/5.

I'm cutting this PR where it currently is, because this is already a huge PR. I believe the remaining tasks will all be easier to land, once this is in. 

## Validation Steps Performed

I've been creating windows, and closing them, and running cmdlines for a while now. I'm gonna keep doing that while the PR is open, till no bugs remain.

# TODOs
* [x] There are a bunch of `GetID`, `GetPID` calls that aren't try/caught 😬 
  -  [x] `Monarch.cpp`
  -  [x] `Peasant.cpp`
  -  [x] `WindowManager.cpp`
  -  [x] `AppHost.cpp`
* [x] If the monarch gets hung, then _you can't launch any Terminals_ 😨 We should handle this gracefully.
  - Proposed idea: give the Monarch some time to respond to a proposal for a commandline. If there's no response in that timeframe, this window is now a _hermit_, outside of society entirely. It can't be elected Monarch. It can't receive command lines. It has no ID.  
  	- Could we gracefully recover from such a state? maybe, probably not though.
    -  Same deal if a peasant hangs, it could end up hanging the monarch, right? Like if you do `wt -w 2`, and `2` is hung, then does the monarch get hung waiting on the hung peasant?
  - After talking with @miniksa, **we're gonna punt this from the initial implementation**. If people legit hit this in the wild, we'll fix it then.
2021-02-10 11:28:09 +00:00
Don-Vito
8b2cdfd1f8 Fix ATS tab status indicators (#9076)
1. Fix progress value not updated
2. Introduce TabStatus object and bind both TabHeaderControl and CommandPalette to it
3. Add support for read-only mode indicator
2021-02-10 11:27:29 +00:00
Don-Vito
47f4b4197d Add support for "focus follows mouse" mode (#8965)
## PR Checklist
* [x] Closes #6459
* [x] CLA signed.
* [ ] Tests added/passed
* [x] Documentation updated here: https://github.com/MicrosoftDocs/terminal/pull/248
* [x] Schema updated.
* [x] I've discussed this with core contributors already.
2021-02-09 22:18:20 +00:00
Dustin L. Howett
6af49a5246 Update Cascadia Code to 2102.03 (#9088)
The February 2021 update of Cascadia Code fixes 23 issues and
introduces support for infinite arrow ligatures and control pictures.
2021-02-09 11:43:45 -08:00
Michael Niksa
5fdad873d3 Update #597 - Tab Sizing.md
spell check fix
2021-02-09 10:50:20 -08:00
Kayla Cinnamon
cad795470f Spec for Tab Sizing (#4104)
This is the spec for #597 

I am proposing the `tabWidthMode` feature be added first, then `tabWidthMin` and `tabWidthMax` be added in a later release.

PR: #3876
2021-02-09 09:08:19 -08:00
PankajBhojwani
a90289548f Quickly fix a mistake in #8602 (#9078)
There was a mistake in #8602, this change fixes that.
(`using` and `include` moved to cpp file)
2021-02-08 17:07:11 -08:00
PankajBhojwani
9047bbbafb Separate runtime TerminalSettings from profile-TerminalSettings (#8602)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
The TerminalSettings object we create from profiles no longer gets passed into the control, instead, a child of that object gets passed into the control. Any overrides the control makes to the settings then live in the child. So, when we do a settings reload, we simply update the child's parent and the overrides will remain.

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manual testing
2021-02-08 22:01:40 +00:00
Carlos Zamora
3b7b200b59 Represent inheritance in Settings UI (#8919)
## Summary of the Pull Request
Introduces the `SettingContainer`. `SettingContainer` is used to wrap a setting in the settings UI and provide the following functionality:
- a reset button next to the header
- tooltips and automation properties for the setting being wrapped
- a comment stating if you are currently overriding a setting

## References
[Spec - Inheritance in Settings UI](https://github.com/microsoft/terminal/blob/main/doc/specs/%231564%20-%20Settings%20UI/cascading-settings.md)
#8804 - removes the ambiguity of leaving a setting blank
#6800 - Settings UI Epic
#8899 - Automation properties for Settings UI
#8768 - Keyboard Navigation

## PR Checklist
* [X] Closes #8804

## Detailed Description of the Pull Request / Additional comments
A few highlights in this PR:
- CommonResources.xaml:
  - we need to merge the SettingContainerStyle.xaml in there. Otherwise, XAML doesn't merge these files properly and can't apply the template.
- Profiles.cpp:
  - view model checks if the starting directory and background image were reset, to determine which value to show when unchecking the special value
  - `Profiles::OnNavigatedTo()` needs a property changed handler to update its own "Current<Setting>" and update the UI properly
- Profiles.xaml:
  - basically wrapped all of the settings we want to be inheritable in there
  - `Binding` is used instead of `x:Bind` in some places because `x:Bind` can't find the parent `SettingContainer` and gives you a compiler error.
- Resources.resw:
  - had to set the "HeaderText" and "HelpText" on each setting container. Does a decent localization burden, unfortunately.
- `SettingContainer` files
  - This operates by creating a template and applying that template over other settings. This allows you to inject the existing controls inside of this. This means that we need to provide our UIElements names and access/modify them via `OnApplyTemplate`
  - We had to remove the header from each individual control, and have `SettingContainer` be in charge of it. This allows us to add the reset button in there.
  - Due to the problem mentioned earlier about CommonResources.xaml, we can't reference anything from CommonResources.xaml.
  - Using `DependencyProperty` to let us set a few properties in the XML files. Particularly, `Has<Setting>` and `Clear<Setting>` are what do all the heavy lifting of interacting with the inheritance model.

## Demo
![Inheritance Demo](https://user-images.githubusercontent.com/11050425/106192086-92a56680-6160-11eb-838c-4ec0beb54965.gif)

## Validation Steps Performed
- Verified correct binding behavior with the following generic setting controls:
  - radio buttons
  - toggle switch
  - text block
  - slider
  - settings with browse buttons
  - the background image alignment control
  - controls with special check boxes (starting directory and background image)

## Next Steps
- The automation properties have been verified using NVDA. This is a part of resolving #8899.
- The override text is currently "Overrides a setting". According to #8269, we actually want to add a hyperlink in there that navigates to the parent profile object. This will be a follow-up task as it requires settings model changes.
2021-02-08 18:04:43 +00:00
Don-Vito
3230b18020 Introduce read-only panes (#8867)
## Summary of the Pull Request
Introduces read-only panes.
When pane is marked as read-only:
1. Attempt to provide user input results in a warning
2. Attempt to close pane - shows dialog
3. Attempt to close hosting tab shows dialog
4. The hosting tab has no close button

## PR Checklist
* [x] Closes #6981
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated - not yet.
* [x] Schema updated.
* [ ] I've discussed this with core contributors already. 

## Detailed Description of the Pull Request / Additional comments
1. The readonly  logic implemented in `TermControl`
(and prevents any send input)
2. Special handling is required to allow key-bindings
3. The "close-readonly" protections are in TerminalPage.
4. The indication that the pane is readonly is done using lock glyph
5. The indication that the tab contains readonly pane
is done by hiding the close button of the tab
6. The readonly mode is enabled by keyboard shortcut
(the followup might add this to the context menu)

## Validation Steps Performed
2021-02-08 18:03:55 +00:00
Don-Vito
a3c8beaba0 Introduce zoom, bell and progress indicators to ATS (#9041)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/8912
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already.

## Detailed Description of the Pull Request / Additional comments
* Introduced separate DataTemplate for rendering TabPaletteItem
* Introduced DataTemplateSelector assigning this template
* Introduced indicators as observable properties of the terminal Tab
* Bound TabPaletteItem to these properties
* Ensured that BindingUpdate is always called on CmdPal to avoid races
2021-02-08 17:37:58 +00:00
Leonard Hecker
b009d06bc3 Fixed #5205: Ctrl+Alt+2 doesn't send ^[^@ (#5272)
## Summary of the Pull Request

Fixes #5205, by replacing another use of `MapVirtualKeyW` with `ToUnicodeEx`.
The latter just seems to be much more consistent at translating key combinations in general.
In this particular case though it fixes the issue, because there's no differentiation in `MapVirtualKeyW` for whether it failed to return a character (`'\0'`) or succeeded in turning `^@` into `'\0'`.
`ToUnicodeEx` on the other hand returns the success state separately from the translated character.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #5205
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
* [ ] Requires documentation to be 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: #5205

## Detailed Description of the Pull Request / Additional comments

This PR changes the behavior of the `Ctrl+Alt+Key` handling slightly:
⚠️ `ToUnicodeEx` returns unshifted characters. ⚠️
For instance `Ctrl+Alt+a` is now turned into `^[^a`. Due to how ASCII works this is essentially the same though because `'A' & 0b11111` and `'a' & 0b11111` are the same.

## Validation Steps Performed

* Run `showkey -a`
* Ensured `Ctrl+Alt+Space` as well as `Ctrl+Alt+Shift+2` are turned into `^[^@`
* Ensured other, random `Ctrl+Alt+Key` combination behave identical to the current master
2021-02-08 15:33:38 +00:00
Chester Liu
2c603ef953 Add support for paste filtering and bracketed paste mode (#9034)
This adds "paste filtering" & "bracketed paste mode" to the Windows
Terminal.

I've moved the paste handling code in `TerminalControl` to
`Microsoft::Console::Util` to be able to easily test it, and the paste
transformer from `TerminalControl` to `TerminalCore`.

Supersedes #7508
References #395 (overall bracketed paste support request)

Tests added. Manually tested.
2021-02-08 13:11:01 +00:00
Don-Vito
5f8e3d1676 Prevent command palette from closing on right click (#9057)
The command palette is ephemeral and is dismissed if the focus
moves to an element which is not the palette's descendant.

Unfortunately, this breaks the (right-click) context menu,
as it  is not a child of the palette
(popups are hosted on a separate root element).
2021-02-06 00:36:47 +00:00
PankajBhojwani
1962767aec Spec: Appearance configuration objects for profiles (#8345)
<!-- 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
Spec for #3062

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #xxx
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [x] Is documentation
* [ ] Schema updated.
* [x] I work here

<!-- 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
Read the spec
2021-02-06 00:05:17 +00:00
Sarim Khan
47881a802f fix tab title propagation issues (#9054)
- Fixes empty app title when  `showTerminalTitleInTitlebar` is false
- Fixes Tab title propagation to Window title when
  `showTerminalTitleInTitlebar` is false
- Fixes Tab title propagation to Window - title doesn't update when
  Window is unfocused

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

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

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

Closes #8704
2021-02-05 23:34:40 +00:00
Mike Griese
207f15498f Spec for Windows Terminal Window Management (#8135)
### ⇒ [doc link](https://github.com/microsoft/terminal/blob/dev/migrie/s/4472-window-management/doc/specs/%235000%20-%20Process%20Model%202.0/%234472%20-%20Windows%20Terminal%20Session%20Management.md) ⇐

## Summary of the Pull Request

This is a more detailed spec for two parts of the "Process Model 2.0" work that's being tracked in #5000. In particular, this spec focuses on the management of Windows Terminal windows, including opening new tabs in existing windows. 

Largely, the reader is expected to have already read the spec in progress in #7240, and already be familiar with the concept of "Monarch" and "Peasant" windows as introduced by that spec. For that reason, ⚠ **THIS PR IS TARGETING THE BRANCH FOR #7240** ⚠. 

### Abstract

> This document is intended to serve as an addition to the [Process Model 2.0
> Spec]. That document provides a big-picture overview of changes to the entirety
> of the Windows Terminal process architecture, including both the split of
> window/content processes, as well as the introduction of monarch/peasant
> processes. The focus of that document was to identify solutions to a set of
> scenarios that were closely intertwined, and establish these solutions would
> work together, without preventing any one scenario from working. What that
> document did not do was prescribe specific solutions to the given scenarios.
>
> This document offers a deeper dive on a subset of the issues in [#5000], to
> describe specifics for managing multiple windows with the Windows Terminal. This
> includes features such as:
>
> * Run `wt` in the current window ([#4472])
> * Single Instance Mode ([#2227])


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

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

### Why are these two separate documents?

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

This doc on the other hand focuses more closely on a pair of scenarios, and establishes how those scenarios will work technically, and how they'll be exposed to the user. 

### TODO:

* [x] A thought - How will we handle arguments like `--fullscreen`, `--initialSize r,c`? They only apply when creating a new window, right?
* [x] When a `wt -s 1 split-pane` command is executed, we'll need to make sure to not _also_ create a new tab
2021-02-05 06:30:02 -06:00
Mike Griese
4cce933f89 Spec for Windows Terminal Process Model 2.0 (#7240)
### ⇒ [doc link](https://github.com/microsoft/terminal/blob/dev/migrie/s/5000/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md) ⇐

## Summary of the Pull Request

This spec is _exceptionally long_, and is currently a work in progress. There are a few more things I'd like to have experimentally verified (though, I'm fairly certain they _will_ work, with the right combination of flags and such). Additionally, a few sections have remaining TODOs before the spec is finished. However, this spec is already fairly long, and I want to give people as much time to get their eyes on it as possible.

### Abstract

> 
> The Windows Terminal currently exists as a single process per window, with one
> connection per terminal pane (which could be an additional conpty process and
> associated client processes). This model has proven effective for the simple
> windowing we've done so far. However, in order to support scenarios like
> dragging tabs into other windows, or having one top-level window with different
> elevation levels within it, this single process model will not be sufficient.
> 
> This spec outlines changes to the Terminal process model in order to enable the
> following scenarios:
> 
> * Tab Tearoff/ Reattach ([#1256])
> * Run `wt` in the current window ([#4472])
> * Single Instance Mode ([#2227])
> * Quake Mode ([#653])
> * Mixed Elevation ([#1032] & [#632])


## PR Checklist
* [x] Specs: #5000
* [x] References: #1256, #4472, #2227, #653, #1032, #632, #492
* [x] I work here

## Detailed Description of the Pull Request / Additional comments
_\*<sup>\*</sup><sub>\*</sub> read the spec  <sub>\*</sub><sup>\*</sup>\*_
2021-02-05 06:19:32 -06:00
Don-Vito
9cb8db8e9a Fix cursor restore to happen on the tab row (#8952)
Closes #8918
2021-02-04 16:44:17 -08:00
Carlos Zamora
230fad533e Serialize 'disabledProfileSources' (#9038)
"disabledProfileSources" is saved to `CascadiaSettings` _not_
`GlobalAppSettings` (and, even then, it's only read when it's used,
never saved). This PR specifically detects if it was defined in
settings.json, and copies it over when the settings are serialized.

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

Closes #9032
2021-02-04 16:42:53 -08:00
Don-Vito
40e328984d Fix hyperlink not de-underlined in unfocused pane (#9039)
Ensures that:
* All hyperlink related logic is running on unfocused pane
* All unrelated logic is not running on unfocused pane

Closes #8925
2021-02-04 16:41:47 -08:00
PankajBhojwani
ed4e829adc Fix crash when closing multiple splits from the tab context menu (#9028)
## Summary of the Pull Request
Fix for #9021 

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

## PR Checklist
* [x] Closes #9021 

## Validation Steps Performed
No more crash
2021-02-04 18:13:26 +00:00
Lachlan Picking
7c42ed4cc3 Add animated shader samples (#9026)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
- Adds two simple animated shaders to the pixel shaders sample folder
- Updates the readme in the pixel shaders sample folder to add a section explaining the animated shaders
- Modifies some comments in existing shader samples

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

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
The two shaders I wrote are not especially pretty or interesting, but they should hopefully serve as simple examples for anyone looking to do animated effects. One simply draws a line of inverted pixels that scrolls down the screen, and the other fades the background back and forth between two colors.

I've added a new section to the readme explaining how the shaders work to achieve animated effects.

I've also updated the comments on the existing shaders to clear up a couple of things:
1. Be more explicit that `Time` represents seconds since the shader loaded. Though obvious in hindsight, this was not clear to me when I was first learning/experimenting
2. Explain that `tex` ranges from 0,0 to 1,1. This is important because, when trying to port GLSL shaders from shadertoy, I at first assumed `fragCoord` and `tex` are the same thing but the former actually ranges from 0,0 to the resolution of the canvas, so some of the math doesn't work out if you just substitute it with `tex`.

Any and all feedback welcome.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
I ran the shaders manually in a dev build of the Terminal. I auto-spellchecked and manually proofread my additions to the readme and verifed the markdown rendering on github.
2021-02-04 07:53:21 -06:00
Chester Liu
0811c572ae Improve OSC 9;9 parsing logic & add tests (#8934)
This PR fixes the parsing of OSC 9;9 sequences with path surrounded by
quotation marks.

Original OSC 9;9 PR: #8330 

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

Closes #8930
2021-02-04 01:10:21 +00:00
Leonard Hecker
619710852c Fix issues in tests.xml and OpenConsole.psm1 (#9011)
* Fix the incorrect terminalCore path in tests.xml
* Change the -TaefArgs argument of Invoke-OpenConsoleTests and
  Invoke-TaefInNewWindow to be []string, allowing
  multiple arguments to be passed to TAEF
2021-02-03 17:05:50 -08:00
Chester Liu
779354d368 Add support for chaining OSC 10-12 (#8999)
This adds the support for chaining OSC 10-12, allowing users to set all
of them at once.

**BREAKING CHANGE**
Before this PR, the OSC 10/11/12 command will only be dispatched iff the
first color is valid. This is no longer true. The new implementation
strictly follows xterm's behavior. Each color is treated independently.
For example, `\e]10;invalid;white\e\\` is effectively `\e]11;white\e\\`.

## Validation Steps Performed

Tests added. Manually tested.

Main OSC color tracking issue: #942
OSC 4 & Initial OSC 10-12 PR: #7578

Closes one item in #942
2021-02-04 00:59:25 +00:00
Kayla Cinnamon
45bee078f0 doc: add UWP docs link to AddASetting.md (#9025)
Since we're following the UWP design guidance, we should recommend the community use it as well when adding new settings to the settings UI.
2021-02-03 16:54:27 -08:00
Josh Soref
42f7403bf5 ci: update to Spell check to 0.0.17a (#9014)
### Plurals and paste tenses
In the past, plurals `foo`+`s` and past tenses `foo`+`ed` were
automatically tolerated. This turned out to be a bad design choice on my
part.

The basic example is that `potatos` would sometimes be treated as a
mistake and sometimes not (depending on the presence of `potato`).

You can see in this PR, that this logic resulted in `Applys` being
accepted as a word along with `AppContainered` -- there's nothing
intrinsically wrong w/ the latter, but unfortunately in order to screen
out the former, my shortcut just couldn't stick around. This means that
the `dictionary`/`expect` files will grow perhaps by a tiny bit, but as
you can see, not really by much.

This is also why `thereses` (a user) was accepted as a word in the past
(therese is in the base dictionary, so `therese` + `s` was acceptable).

### Pull requests

When GitHub initially introduced GitHub Actions, the event for
`pull_request` was created without enough permission for a tool like
this to work properly. I worked around that by using the `schedule`
event. In 2020, they introduced a replacement event
`pull_request_target` which has enough permission. This means that I can
stop relying on the `schedule` event.

### Miscellaneous

* I've folded together some `expect/` files since now is as good a time
  as any.
* I've included a hint about `excludes.txt` (I added a similar one for
  our primary repo recently, and it came up this week in
  `microsoft/terminal` -- @zadjii-msft)
* I've standardized on a default of `.github/actions/spelling` to make
  the out of the box experience easier for new adopters, so I'm applying
  that change here -- if you're attached to the old directory name,
  specifying it is still supported. -- note the directory rename may
  cause a merge conflict for people with open PRs and changes to the
  contents, this shouldn't be a big problem.
2021-02-03 11:17:38 -08:00
Carlos Zamora
a5931fbead Generate settings.json if deleted while WT is open (#9012)
The settings.json was not regenerated if WT was already open. This resulted in the `ShellExecute` from trying to open the settings to pop up Notepad and say that this file didn't exist. We now detect if the settings.json was deleted to kick off loading the settings.

Closes #8955
2021-02-03 10:45:11 +00:00
Kayla Cinnamon
6b7149d9e6 Nit: update string in cursor shape (#8852)
This should technically be a lowercase 'u'. We don't have to merge this until after 1.6 because of localization, but I figured I'd make the PR for it.
2021-02-02 17:29:17 +00:00
Leonard Hecker
3b9e6124e0 Fix #8458: Handle all Ctrl-key combinations (#8870)
Pressing Ctrl+\ produces `^\` using the US keyboard layout, thanks to Ctrl-key mappings inside the keyboard layout, whereas some layouts, like the UK extended layout, don't contain those. This causes the character value to be zero and previously caused no VT sequence to be generated under these situations. This PR employs `MapVirtualKeyW` to infer the missing characters.
As a side effect this PR effectively causes _all_ major keys on the keyboard to produce Ctrl+combinations now.

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

## Validation Steps Performed

Compared all major keys in combination with Ctrl with the app store version of Terminal using `showkey` in WSL. All keys that previously worked still appear to continue to work.
2021-02-02 17:26:48 +00:00
Mike Griese
92b23700d3 Fix the spellcheck bot (#9004)
@jsoref said to do this, so I'm doing it to fix the bot.
2021-02-02 17:21:43 +00:00
Lachlan Picking
9fb4fb2741 Fix shader time input (#8994)
Correctly sets the time input on the pixelShaderSettings struct, which was previously hard-coded to `0.0f`. 

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

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

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

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

## Validation Steps Performed
I manually tested using a variety of simple shaders that rely on time input for animation.
2021-02-02 17:12:04 +00:00
Mike Griese
9d71fa817d Manually initialize the warnings vector to prevent a crash on launch (#8995)
## Summary of the Pull Request

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

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

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

    "schemes" :
    [ {} ]
```
First the crash repro'd, now it doesn't.
2021-02-02 06:50:31 +00:00
James Holderness
40ebe5ab54 Fix crash in terminal when tab closed while data is being output (#8982)
If there is data being output when a tab is closed, that can sometimes
result in the application crashing, because the renderer may still be in
use at the time is it destroyed. This PR attempts to prevent that from
happening by adding a lock in the `TermControl::Close` method.

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

## Validation Steps Performed

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

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

Closes #8734
2021-02-01 21:28:47 +00:00
Brian
20152d9756 Added some example images for the pixel shader feature (#8962)
I added some sample screenshots on the README for the pixel shader feature

Fixes #8960

## Detailed Description of the Pull Request / Additional comments
I created 4 screenshots using preview windows terminal, one for the default look, and three for the shaders mentioned in the readme. 

For the first shader, `Invert.hlsl`, I put it in a table and placed a screenshot of the default next to it. I don't think it would be clear what the shader did without being able to compare it against the default.

For `Rasterbars.hlsl` I put a screenshot right after the code sample for it. I added a screenshot for `Retro.hlsl` just before the last sentence

I also created a folder named Screenshots in `terminal/samples/Pixelshaders` to hold all of the screenshots.
2021-02-01 12:39:47 -08:00
hereafter
e207236713 Fix crash in explorer background context menu logic (#8977)
Fix a bug brought in with PR: #8638 


see,
#8936 
#8638

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


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

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


Another debugging warning dialogs  for reusing not null com_ptr in the loop is fixed too.
(This was shown in debug builds only)
2021-02-01 17:30:54 +00:00
Carlos Zamora
597a3325ea Bind LaunchMode and TabSwitcherMode properly in SUI (#8956)
## Summary of the Pull Request
Properly binds `CurrentLaunchMode` and `CurrentTabSwitcherMode` in the Settings UI. The default mode is `OneTime`, resulting in the setting never being set.

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

## References
#6800 - Settings UI Epic

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

Closes #8947
2021-02-01 16:38:52 +00:00
Don-Vito
636f436465 Prevent context menu of tab renamer text box from canceling edit (#8979)
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/issues/8975
* [x] CLA signed. 
* [ ] Tests added/passed
* [ ] Documentation updated. 
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. 

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

![RenamerContextMenu](https://user-images.githubusercontent.com/4639110/106394866-90b10100-6407-11eb-8e92-627be4f70500.gif)
2021-02-01 16:37:15 +00:00
jantari
e7d32625be Fix pixel shader typo in README (#8949)
Fixes a typo in the README regarding pixel shaders.
2021-01-29 10:06:36 -08:00
Mike Powell
37cbcc3e2b shaders: fix setting name in README.md (#8926)
Example shows attribute `experimental.pixelShaderEffect`, should be `experimental.pixelShaderPath`
2021-01-28 14:21:30 -06:00
Kayla Cinnamon
b502e0e530 Add scrollToTop and scrollToBottom actions to JSON schema (#8923)
Closes #8992.
2021-01-28 09:38:06 -08:00
Dustin Howett
d29d72e1e0 verison: bump to 1.7 on main 2021-01-27 12:48:29 -08:00
Javier
96e0232603 wpf: Add a TerminalControlSize emptiness check (#8906)
Watson reports show that an "ArgumentException" is being thrown due to `renderSize`
not being valid. Added a check for renderSize before attempting to resize.
2021-01-26 13:57:30 -08:00
Dustin Howett
054d7dbb1c Merged PR 5612120: Migrate OSS up to ae8347f33
Related work items: MSFT-31478342
2021-01-26 19:39:20 +00:00
Carlos Zamora
ae8347f336 Allow ClosePane to close the Settings UI (#8903)
This is an extension of #8885. A lot of users have grown accustomed to
using `closePane` to close a tab. This adds `closePane` to the list of
keybindings accepted by #8885, and modifies the `closePane` code to
close the Settings UI if we are in a `SettingsTab`.

## References

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Base Layer does not have a "Name".

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

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

## References
#6800 - Settings UI Epic

## PR Checklist
* [X] Closes #8756 

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

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

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

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

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

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

References #395
Supersedes #7508
2021-01-22 05:11:11 +00:00
Dustin Howett
b208a83666 Merged PR 5598783: Merge LNK Fix (6b2ae625a)
Initialize stack variables. check return code from shell lnk loading (GH-8712)

## References
* Commit f273aa679d4d9fb516678fc7ed5fc6495a8e8532 from os repository.

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

## Validation Steps Performed
* [x] Manual scenario running LNK file that cannot be loaded from customer report (make LNK to cmd.exe, make it inaccessible to conhost so it can't load/read it.)
* [x] Manual scenario with `mklink`'d link that makes the shortcut parser attempt to load (and fail because it isn't a LNK at all from GH-7650)

Fixes MSFT-31351620
2021-01-22 05:05:33 +00:00
Dustin Howett
8b855ca88c Merged PR 5598201: Reflect OS build changes atop a8b404463
The Windows build system doesn't support PMR yet, so we had to add a workaround. :(

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev 1614b27830a07484c18628ec7d9abc5f9d9b8a0b

Related work items: MSFT-31337042
2021-01-22 04:57:26 +00:00
Carlos Zamora
b6f5f2a323 Revert "Maintain current Pivot selection... (#8803)" (#8838)
This reverts commit a7d7362b95 introduced by #8803.

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

## References
#6800 - Settings UI Epic

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### CharRow: Move DoubleBytePadded into ROW

### CharRow: Move WrapForced into ROW

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

### Remove RowCellIterator (dead code)

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

### Move AttrRowTests to ut_textbuffer from ut_host

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

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

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

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

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

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

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

References #6800 - Settings UI Epic

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

Also performed repro steps for #8747 and #8748.

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

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

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

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

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

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

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

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

This PR fixes two of the components of #8765. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Validation Steps Performed

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

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

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

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

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

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

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

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

The only downsides are...

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

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

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

Introduces a `startupActions` global setting. 

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

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

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

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

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manual validation.
2021-01-15 17:48:14 +00:00
Michael Niksa
69538c4ab3 code format 2021-01-14 16:30:16 -08:00
Michael Niksa
324c0942c8 spell check 2021-01-14 16:29:33 -08:00
Michael Niksa
c65161d60e Let full batch assign through to public. Make AttrRow use it. TextAttributeRun is now just a std::pair<> so it fits nicely in the AttrRow which is just a til::rle now. Add tests. 2021-01-14 16:20:14 -08:00
PankajBhojwani
f7b5ff322a Add missing settings to the settings UI (#8774)
Adds the tab switcher mode setting and copy format setting to the SUI

Closes #8755
2021-01-14 23:57:59 +00:00
Michael Niksa
b591355103 Drop attrrowtests.cpp. It overlaps/is obsoleted by RunLengthEncodingTests.cpp 2021-01-14 15:45:57 -08:00
Michael Niksa
a1c1d5cc09 Make AttrRow use til::rle directly. 2021-01-14 15:44:34 -08:00
Michael Niksa
67d591b11d Iterators and tests. 2021-01-14 14:45:47 -08:00
PankajBhojwani
9905bd5f09 Add missing functionality to SUI (#8786)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Add checkbox for 'inherit from parent process' for starting directory
When checked, the textbox and browse button are disabled
If the starting directory is empty, the checkbox is automatically checked

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

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

References #8764 

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

## References
#6800 - Settings UI Epic

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

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

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

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

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

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


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

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

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

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

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

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

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

## References
* Missed in #8543


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

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

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

Closes #2991
2021-01-12 20:23:40 +00:00
Michael Niksa
33e9a9b22a It compiles! 2021-01-11 15:50:10 -08:00
Mike Griese
7235996b4d Add a move-focus subcommand (#8546)
## Summary of the Pull Request

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

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

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

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

## Validation Steps Performed

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

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

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

This PR implement the OSC 9;9 

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


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

#8214

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

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-01-11 18:01:38 +00:00
Michael Niksa
8114fa0dd0 it doesn't quite work yet, but it's what I'm going for. 2021-01-08 16:32:01 -08:00
Michael Niksa
00ca3f204c Clean out the rect stuff and prep for rle stuff. 2021-01-08 12:52:42 -08:00
Michael Niksa
1ed8ef0847 copy rectangle to rle and add to sources. 2021-01-08 12:48:15 -08:00
PankajBhojwani
49d008537f Fix multi line paste detection and filtering (#8634)
- Detect `\r` when warning about multi line paste
- Translate `\n` to `\r` on paste

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

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

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

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

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

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

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

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

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

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

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

## References

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

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

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

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

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

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

## Validation Steps Performed

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

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

## References
#6800 - Settings UI epic

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

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

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

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

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

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

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

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

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

## Validation Steps Performed
* Manual testing

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

## Validation Steps Performed

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

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

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

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

## Detailed Description of the Pull Request / Additional comments

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Austin Lamb <austinl@microsoft.com>
2020-12-16 10:40:30 -08:00
Dustin Howett
857a893660 Merged PR 5503403: Migrate OSS up to 05c0d4c0e
N
* Change backslashes in include statements to forward slashes (CC-8205)

Dustin L. Howett
* Refactor DEC/ANSI modes to avoid duplication when we add SM/RM (GH-8469)
* Fix the xterm and SGR mouse encodings for CTRL, ALT, SHIFT (GH-8379)

James Holderness
* Fix rendering of DBCS characters when partially off screen (CC-8438)
* Correct horizontal coordinates in viewport overflow test (CC-8456)
* Correct paths in the the runut and runft test scripts (CC-8488)
* Retain horizontal viewport offset when moving to bottom (CC-8434)
* Fix out-of-bounds exceptions in Set...{Buffer,Screen}Size (CC-8309)

PankajBhojwani (1)
* Implement ConEmu's OSC 9;4 to set the taskbar progress indicator (CC-8055)

Chester Liu (1)
* Improve OSC 8 Hyperlink parsing logic (CC-7962)

Related work items: #31090459, #31090805, #31090808, #31090810
2020-12-16 01:36:03 +00:00
Dustin Howett
7241fa29c6 Merged PR 5445070: [Git2Git] Update the Windows build for some shell changes
Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_onecore_dep_uxp 91dff13f6953441afe9e28bfe3da9d5bff27bb05

Related: MSFT-26196390

Related work items: #26196390
2020-12-16 01:32:35 +00:00
592 changed files with 29924 additions and 10313 deletions

View File

@@ -1,54 +0,0 @@
---
name: "Bug report 🐛"
about: Report errors or unexpected behavior
title: ''
labels: ''
assignees: ''
---
<!--
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.
All good? Then proceed!
-->
<!--
This bug tracker is monitored by Windows Terminal development team and other technical folks.
**Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**.
Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue.
If this is an application crash, please also provide a Feedback Hub submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal (Preview)" and choose "Share My Feedback" after submission to get the link.
Please use this form and describe your issue, concisely but precisely, with as much detail as possible.
-->
# Environment
```none
Windows build number: [run `[Environment]::OSVersion` for powershell, or `ver` for cmd]
Windows Terminal version (if applicable):
Any other software?
```
# Steps to reproduce
<!-- A description of how to trigger this bug. -->
# Expected behavior
<!-- A description of what you're expecting, possibly containing screenshots or reference material. -->
# Actual behavior
<!-- What's actually happening? -->

57
.github/ISSUE_TEMPLATE/Bug_Report.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: "Bug report 🐛"
description: Report errors or unexpected behavior
title: ''
labels: ''
assignees: ''
issue_body: true
body:
- type: markdown
attributes:
value: |
Please make sure to [search for existing issues](https://github.com/microsoft/terminal/issues) before filing a new one!
If this is an application crash, please also provide a [Feedback Hub](https://aka.ms/terminal-feedback-hub) submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal" and choose "Share My Feedback" after submission to get the link.
- type: input
attributes:
label: Windows Terminal version (or Windows build number)
placeholder: "10.0.19042.0, 1.7.3651.0"
description: |
If you are reporting an issue in Windows Terminal, you can find the version in the about dialog.
If you are reporting an issue with the Windows Console, please run `ver` or `[Environment]::OSVersion`.
validations:
required: true
- type: textarea
attributes:
label: Other Software
description: If you're reporting a bug about our interaction with other software, what software? What versions?
placeholder: |
vim 8.2 (inside WSL)
OpenSSH_for_Windows_8.1p1
My Cool Application v0.3 (include a code snippet if it would help!)
validations:
required: false
- type: textarea
attributes:
label: Steps to reproduce
placeholder: Tell us the steps required to trigger your bug.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: If you want to include screenshots, paste them into the markdown editor below the form or follow up with a separate comment.
placeholder: What were you expecting?
validations:
required: false
- type: textarea
attributes:
label: Actual Behavior
placeholder: What happened instead?
validations:
required: true

13
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-vscode.cpptools"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

24
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug OpenConsole by Launching (x64, debug)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\bin\\x64\\debug\\openconsole.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
},
{
"name": "Debug Terminal by Attaching (You go build/register/launch it first.)",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

32
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,32 @@
{
"C_Cpp.default.browse.databaseFilename": "${workspaceFolder}\\.vscode\\.BROWSE.VC.DB",
"C_Cpp.default.browse.path": [
"${workspaceFolder}"
],
"C_Cpp.loggingLevel": "None",
"files.associations": {
"xstring": "cpp",
"*.idl": "cpp",
"array": "cpp",
"future": "cpp",
"istream": "cpp",
"memory": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"variant": "cpp",
"xlocmes": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xloctime": "cpp",
"multi_span": "cpp",
"pointers": "cpp",
"vector": "cpp"
},
"files.exclude": {
"**/bin/**": true,
"**/obj/**": true,
"**/packages/**": true,
"**/generated files/**": true
}
}

107
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,107 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "process",
"label": "Build Terminal/Console",
"command": "powershell.exe",
"args": [
"-Command",
"Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;",
"Set-MsBuildDevEnvironment;",
"$project = switch(\"${input:buildProjectChoice}\"){OpenConsole{\"Conhost\\Host_EXE\"} Terminal{\"Terminal\\CascadiaPackage\"}};",
"$target = switch(\"${input:buildModeChoice}\"){Build{\"\"} Rebuild{\":Rebuild\"} Clean{\":Clean\"}};",
"$target = $project + $target;",
"msbuild",
"${workspaceFolder}\\OpenConsole.sln",
"/p:Configuration=${input:configChoice}",
"/p:Platform=${input:platformChoice}",
"/t:$target",
"/verbosity:minimal"
],
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
"isDefault": true
},
"runOptions": {
"reevaluateOnRerun": false,
"instanceLimit": 1,
"runOn": "default"
}
},
{
"type": "process",
"label": "Register Windows Terminal x64 Debug",
"command": "powershell.exe",
"args": [
"-Command",
"Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;",
"Set-MsBuildDevEnvironment;",
"Set-Location -Path ${workspaceFolder}\\src\\cascadia\\CascadiaPackage\\AppPackages\\CascadiaPackage_0.0.1.0_x64_Debug_Test;",
"if ((Get-AppxPackage -Name 'WindowsTerminalDev*') -ne $null) { Remove-AppxPackage 'WindowsTerminalDev_0.0.1.0_x64__8wekyb3d8bbwe'};",
"New-Item ..\\loose -Type Directory -Force;",
"makeappx unpack /v /o /p .\\CascadiaPackage_0.0.1.0_x64_Debug.msix /d ..\\Loose\\;",
"Add-AppxPackage -Path ..\\loose\\AppxManifest.xml -Register -ForceUpdateFromAnyVersion -ForceApplicationShutdown"
],
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "process",
"label": "Run Windows Terminal Dev",
"command": "wtd.exe",
"args": [
],
"problemMatcher": ["$msCompile"],
}
],
"inputs":[
{
"id": "platformChoice",
"type": "pickString",
"description": "Processor architecture choice",
"options":[
"x64",
"x86",
"arm64"
],
"default": "x64"
},
{
"id": "configChoice",
"type": "pickString",
"description": "Debug or release?",
"options":[
"Debug",
"Release"
],
"default": "Debug"
},
{
"id": "buildModeChoice",
"type": "pickString",
"description": "Build, rebuild, or clean?",
"options":[
"Build",
"Rebuild",
"Clean"
],
"default": "Build"
},
{
"id": "buildProjectChoice",
"type": "pickString",
"description": "OpenConsole or Terminal?",
"options":[
"OpenConsole",
"Terminal"
],
"default": "Terminal"
}
]
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -33,10 +33,23 @@ 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
For users who are unable to install Windows Terminal from the Microsoft Store,
released builds can be manually downloaded from this repository's [Releases
page](https://github.com/microsoft/terminal/releases).
Download the `Microsoft.WindowsTerminal_<versionNumber>.msixbundle` file from
the **Assets** section. To install the app, you can simply double-click on the
`.msixbundle` file, and the app installer should automatically run. If that
fails for any reason, you can try the following command at a PowerShell prompt:
```powershell
# NOTE: If you are using PowerShell 7+, please run
# Import-Module Appx -UseWindowsPowerShell
# before using Add-AppxPackage.
Add-AppxPackage Microsoft.WindowsTerminal_<versionNumber>.msixbundle
```
> 🔴 Note: If you install Terminal manually:
>
> * Terminal will not auto-update when new builds are released so you will need

View File

@@ -58,7 +58,7 @@ Try {
### Check the activatable class entries for a few DLLs we need.
$inProcServers = $Manifest.Package.Extensions.Extension.InProcessServer.Path
$RequiredInProcServers = ("TerminalApp.dll", "TerminalControl.dll", "TerminalConnection.dll")
$RequiredInProcServers = ("TerminalApp.dll", "Microsoft.Terminal.Control.dll", "Microsoft.Terminal.Remoting.dll", "Microsoft.Terminal.Settings.Editor.dll", "Microsoft.Terminal.Settings.Model.dll", "TerminalConnection.dll")
Write-Verbose "InProc Servers: $inProcServers"

View File

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

View File

@@ -3,9 +3,9 @@
<!-- This file is read by XES, which we use in our Release builds. -->
<PropertyGroup Label="Version">
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2021</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>6</VersionMinor>
<VersionMinor>8</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

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

View File

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

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

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

View File

@@ -76,9 +76,11 @@
"copy",
"duplicateTab",
"find",
"findMatch",
"moveFocus",
"moveTab",
"newTab",
"newWindow",
"nextTab",
"openNewTabDropdown",
"openSettings",
@@ -93,6 +95,8 @@
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"scrollToBottom",
"scrollToTop",
"sendInput",
"setColorScheme",
"setTabColor",
@@ -103,6 +107,7 @@
"toggleFocusMode",
"toggleFullscreen",
"togglePaneZoom",
"toggleReadOnlyMode",
"toggleShaderEffects",
"wt",
"unbound"
@@ -135,6 +140,13 @@
],
"type": "string"
},
"FindMatchDirection": {
"enum": [
"next",
"prev"
],
"type": "string"
},
"SplitState": {
"enum": [
"vertical",
@@ -210,10 +222,28 @@
"$ref": "#/definitions/Color",
"default": null,
"description": "If provided, will set the tab's color to the given value"
},
"suppressApplicationTitle": {
"type": "boolean",
"default": "false",
"description": "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"
}
},
"type": "object"
},
"SwitchToAdjacentTabArgs" : {
"oneOf": [
{ "type": "null" },
{
"enum": [
"mru",
"inOrder",
"disabled"
],
"type": "string"
}
]
},
"ShortcutAction": {
"properties": {
"action": {
@@ -369,6 +399,13 @@
"splitMode": {
"default": "duplicate",
"description": "Control how the pane splits. Only accepts \"duplicate\" which will duplicate the focused pane's profile into a new pane."
},
"size": {
"default": 0.5,
"description": "Specify how large the new pane should be, as a fraction of the current pane's size. 1.0 would be 'all of the current pane', and 0.0 is 'None of the parent'. Accepts floating point values from 0-1 (default 0.5).",
"maximum": 1,
"minimum": 0,
"type": "number"
}
}
}
@@ -549,6 +586,67 @@
}
]
},
"FindMatchAction": {
"description": "Arguments corresponding to a Find Match Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "findMatch" },
"direction": {
"$ref": "#/definitions/FindMatchDirection",
"default": "prev",
"description": "The direction to search in. \"prev\" will search upwards in the buffer, and \"next\" will search downwards."
}
}
}
],
"required": [ "direction" ]
},
"NewWindowAction": {
"description": "Arguments corresponding to a New Window Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{ "$ref": "#/definitions/NewTerminalArgs" },
{
"properties": {
"action": { "type":"string", "pattern": "newWindow" }
}
}
]
},
"PrevTabAction": {
"description": "Arguments corresponding to a Previous Tab Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type":"string", "pattern": "prevTab" },
"tabSwitcherMode": {
"$ref": "#/definitions/SwitchToAdjacentTabArgs",
"default": null,
"description": "Move to the previous tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one."
}
}
}
]
},
"NextTabAction": {
"description": "Arguments corresponding to a Next Tab Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type":"string", "pattern": "nextTab" },
"tabSwitcherMode": {
"$ref": "#/definitions/SwitchToAdjacentTabArgs",
"default": null,
"description": "Move to the next tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one."
}
}
}
]
},
"Keybinding": {
"additionalProperties": false,
"properties": {
@@ -573,6 +671,10 @@
{ "$ref": "#/definitions/ScrollUpAction" },
{ "$ref": "#/definitions/ScrollDownAction" },
{ "$ref": "#/definitions/MoveTabAction" },
{ "$ref": "#/definitions/FindMatchAction" },
{ "$ref": "#/definitions/NewWindowAction" },
{ "$ref": "#/definitions/NextTabAction" },
{ "$ref": "#/definitions/PrevTabAction" },
{ "type": "null" }
]
},
@@ -620,11 +722,26 @@
"description": "When set to true, tabs are always displayed. When set to false and \"showTabsInTitlebar\" is set to false, tabs only appear after opening a new tab.",
"type": "boolean"
},
"centerOnLaunch": {
"default": false,
"description": "When set to `true`, the terminal window will auto-center itself on the display it opens on. The terminal will use the \"initialPosition\" to determine which display to open on.",
"type": "boolean"
},
"inputServiceWarning": {
"default": true,
"description": "Warning if 'Touch Keyboard and Handwriting Panel Service' is disabled.",
"type": "boolean"
},
"copyOnSelect": {
"default": false,
"description": "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.",
"type": "boolean"
},
"focusFollowMouse": {
"default": false,
"description": "When set to true, the terminal will focus the pane on mouse hover.",
"type": "boolean"
},
"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. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
@@ -649,6 +766,10 @@
"description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".",
"type": "string"
},
"startupActions": {
"description": "Sets the list of actions to apply if no command line is provided. Uses the same format as command line arguments",
"type": "string"
},
"disabledProfileSources": {
"description": "Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup.",
"items": {
@@ -793,6 +914,16 @@
"type": "string"
}
]
},
"windowingBehavior": {
"default": "useNew",
"description": "Controls how new terminal instances attach to existing windows. \"useNew\" will always create a new window. \"useExisting\" will create new tabs in the most recently used window on this virtual desktop, and \"useAnyExisting\" will create tabs in the most recent window on any desktop.",
"enum": [
"useNew",
"useExisting",
"useAnyExisting"
],
"type": "string"
}
},
"required": [
@@ -914,17 +1045,18 @@
"description": "Sets the color of the cursor. Overrides the cursor color from the color scheme. Uses hex color format: \"#rrggbb\"."
},
"cursorHeight": {
"description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 25-100.",
"description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 1-100.",
"maximum": 100,
"minimum": 25,
"minimum": 1,
"type": ["integer","null"],
"default": 25
},
"cursorShape": {
"default": "bar",
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"doubleUnderscore\" ( ‗ )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
"enum": [
"bar",
"doubleUnderscore",
"emptyBox",
"filledBox",
"underscore",

View File

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

View File

@@ -0,0 +1,128 @@
---
author: Pankaj Bhojwani, pabhojwa@microsoft.com
created on: 2020-11-20
last updated: 2021-2-5
issue id: #8345
---
# Appearance configuration objects for profiles
## Abstract
This spec outlines how we can support 'configuration objects' in our profiles, which
will allow us to render differently depending on the state of the control. For example, a
control can be rendered differently if it's focused as compared to when it's unfocused.
## Inspiration
Reference: [#3062](https://github.com/microsoft/terminal/issues/3062)
Users want there to be a more visible indicator than the one we have currently for which
pane is focused and which panes are unfocused. This change would grant us that feature.
## Solution Design
The implementation design for appearance config objects centers around the recent change where inheritance was added to the
`TerminalSettings` class in the Terminal Settings Model - i.e. different `TerminalSettings` objects can inherit from each other.
The reason for this change was that we did not want a settings reload to erase any overrides `TermControl` may have made
to the settings during runtime. By instead passing a child of the `TerminalSettings` object to the control, we can change
the parent of the child during a settings reload without the overrides being erased (since those overrides live in the child).
The idea behind unfocused appearance configurations is similar. We will pass in another `TerminalSettings` object to the control,
which is simply a child that already has some overrides in it. When the control gains or loses focus, it simply switches between
the two settings objects appropriately.
### Allowed parameters
For now, these states are meant to be entirely appearance-based. So, not all parameters which can be
defined in a `Profile` can be defined in this new object (for example, we do not want parameters which
would cause a resize in this object.) Here is the list of parameters we will allow:
- Anything regarding colors: `colorScheme`, `foreground`, `background`, `cursorColor` etc
- Anything regarding background image: `path`, `opacity`, `alignment`, `stretchMode`
- `cursorShape`
We may wish to allow further parameters in these objects in the future (like `bellStyle`?). The addition
of further parameters can be discussed in the future and is out of scope for this spec.
### Inheritance
The inheritance model can be thought of as an 'all-or-nothing' approach in the sense that the `unfocusedAppearance` object
is considered as a *single* setting instead of an object with many settings. We have chosen this model because it is cleaner
and easier to understand than the alternative, where each setting within an `unfocusedAppearance` object has a parent from which
it obtains its value.
Note that when `TerminalApp` initializes a control, it creates a `TerminalSettings` object for that profile and passes the
control a child of that object (so that the control can store overrides in the child, as described earlier). If an unfocused
config is defined in the profile (or in globals/profile defaults), then `TerminalApp` will create a *child of that child*,
put the relevant overrides in it, and pass that into the control as well. Thus, the inheritance of any undefined parameters
in the unfocused config will be as follows:
1. The unfocused config specified in the profile (or in globals/profile defaults)
2. Overrides made by the terminal control
3. The parent profile
## UI/UX Design
Users will be able to add a new setting to their profiles that will look like this:
```
"unfocusedAppearance":
{
"colorScheme": "Campbell",
"cursorColor": "#888",
"cursorShape": "emptyBox",
"foreground": "#C0C0C0",
"background": "#000000"
}
```
When certain appearance settings are changed via OSC sequences (such as the background color), only the focused/regular
appearance will change and the unfocused one will remain unchanged. However, since the unfocused settings object inherits
from the regular one, it will still apply the change (provided it does not define its own value for that setting).
## Capabilities
### Accessibility
Does not affect accessibility.
### Security
Does not affect security.
### Reliability
This is another location in the settings where parsing/loading the settings may fail. However, this is the case
for any new setting we add so I would say that this is a reasonable cost for this feature.
### Compatibility
Should not affect compatibility.
### Performance, Power, and Efficiency
Rapidly switching between many panes, causing several successive appearance changes in a short period of time, could
potentially impact performance. However, regular/reasonable pane switching should not have a noticeable effect.
## Potential Issues
Inactive tabs will be 'rendered' in the background with the `UnfocusedRenderingParams` object, we need to make
sure that switching to an inactive tab (and so causing the renderer to update with the 'normal' parameters)
does not cause the window to flash/show a jarring indicator that the rendering values changed.
## Future considerations
We will need to decide how this will look in the settings UI.
We may wish to add more states in the future (like 'elevated'). When that happens, we will need to deal with how
these appearance objects can scale/layer over each other. We had a lot of discussion about this and could not find
a suitable solution to the problem of multiple states being valid at the same time (like unfocused and elevated).
This, along with the fact that it is uncertain if there even will be more states we would want to add led us to
the conclusion that we should only support the unfocused state for now, and come back to this issue later. If there
are no more states other than unfocused and elevated, we could allow combining them (like having an 'unfocused elevated' state).
If there are more states, we could do the implementation as an extension rather than inherently supporting it.
## Resources

View File

@@ -0,0 +1,562 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-10-30
last updated: 2020-02-05
issue id: #4472
---
# Windows Terminal Session Management
## Abstract
This document is intended to serve as an addition to the [Process Model 2.0
Spec]. That document provides a big-picture overview of changes to the entirety
of the Windows Terminal process architecture, including both the split of
window/content processes, as well as the introduction of monarch/peasant
processes. The focus of that document was to identify solutions to a set of
scenarios that were closely intertwined, and establish these solutions would
work together, without preventing any one scenario from working. What that
document did not do was prescribe specific solutions to the given scenarios.
This document offers a deeper dive on a subset of the issues in [#5000], to
describe specifics for managing multiple windows with the Windows Terminal. This
includes features such as:
* Run `wt` in the current window ([#4472])
* Single Instance Mode ([#2227])
## Solution Design
### Monarch and Peasant Processes
This document assumes the reader is already familiar with the "Monarch and
Peasant" architecture as detailed in the [Windows Terminal Process Model 2.0
Spec]. As a quick summary:
* Every Windows Terminal window is a "Peasant" process.
* One of the Windows Terminal window processes is also the "Monarch" process.
The Monarch is picked randomly from the Terminal windows, and there is only
ever one Monarch process at a time.
* Peasants can communicate with the monarch when certain state changes (such as
their window being activated), and the monarch can send commands to any of the
peasants.
This architecture will be used to enable each of the following scenarios.
### Scenario: Open new tabs in most recently used window
A common feature of many browsers is that when a web URL is clicked somewhere,
the web page is opened as a new tab in the most recently used window of the
browser. This functionality is often referred to as "glomming", as the new tab
"gloms" onto the existing window.
Currently, the terminal does not support such a feature - every `wt` invocation
creates a new window. With the monarch/peasant architecture, it'll now be
possible to enable such a scenario.
As each window is activated, it will call a method on the `Monarch` object
(hosted by the monarch process) which will indicate that "I am peasant N, and
I've been focused". The monarch will use those method calls to update its own
internal stack of the most recently used windows.
Whenever a new `wt.exe` process is launched, that process will _first_ ask the
monarch if it should run the commandline in an existing window, or create its
own window.
![auto-glom-wt-exe](auto-glom-wt-exe.png)
If glomming is enabled, the monarch will dispatch the commandline to the
appropriate window for them to handle instead. To the user, it'll seem as if the
tab just opened in the most recent window.
Users should certainly be able to specify if they want new instances to glom
onto the MRU window or not. You could imagine that currently, we default to the
hypothetical value `"windowingBehavior": "useNew"`, meaning that each new wt gets
its own new window.
If glomming is disabled, then the Monarch will call back to the peasant and tell
it to run the provided commandline. The monarch will use the return value of
`ExecuteCommandline` to indicate that the calling process should create a window
and become a peasant process, and run the commandline itself.
#### Glomming within the same virtual desktop
When links are opened in the new Edge browser, they will only glom onto an
existing window if that window is open in the current virtual desktop. This
seems like a good idea of a feature for the Terminal to follow as well.
There must be some way for an application to determine which virtual desktop it
is open on. We could use that information to have the monarch track the last
active window _per-desktop_, and only glom when there's one on the current
desktop.
We could make the `windowingBehavior` property accept a variety of
configurations:
- `"useExisting"`: always glom to the most recent window, regardless of desktop.
- `"useExistingOnSameDesktop"`: Only glom if there's an existing window on this
virtual desktop, otherwise create a new window. This will be the new default
value.
- `"useNew"`: Never glom, always create a new window. This is technically the
current behavior of the Terminal.
### Handling the current working directory
Consider the following scenario: the user runs `wt -d .` in the address bar of
explorer, and the monarch determines that this new tab should be created in an
existing window. For clarity during this example, we will label the existing
window WT[1], and the second `wt.exe` process WT[2].
An example of this scenario is given in the following diagram:
![single-instance-mode-cwd](single-instance-mode-cwd.png)
In this scenario, we want the new tab to be spawned in the current working
directory of WT[2], not WT[1]. So when WT[1] is about to run the commands that
were passed to WT[2], WT[1] will need to:
* First, stash its own CWD
* Change to the CWD of WT[2]
* Run the commands from WT[2]
* Then return to its original CWD.
So, as a part of the interface that a peasant uses to communicate the startup
commandline to the monarch, we should also include the current working
directory.
### Scenario: Run `wt` in the current window
One often requested scenario is the ability to run a `wt.exe` commandline in the
current window, as opposed to always creating a new window. Presume we have the
ability to communicate between different window processes. The logical extension
of this scenario would be "run a `wt` commandline in _any_ given WT window".
Each window process will have its own unique ID assigned to it by the monarch.
This ID will be a positive number. Windows can also have names assigned to them.
These names are strings that the user specifies. A window will always have an
ID, but not necessarily a name. Running a command in a given window with ID N
should be as easy as something like:
```sh
wt.exe --window N new-tab ; split-pane
```
(or for shorthand, `wt -w N new-tab ; split-pane`).
More formally, we will add the following parameter to the top-level `wt`
command:
#### `--window,-w <window-id>`
Run these commands in the given Windows Terminal session. This enables opening
new tabs, splits, etc. in already running Windows Terminal windows.
* If `window-id` is `0`, run the given commands in _the current window_.
* If `window-id` is a negative number, or the reserved name `new`, run the
commands in a _new_ Terminal window.
* If `window-id` is the ID or name of an existing window, then run the
commandline in that window.
* If `window-id` is _not_ the ID or name of an existing window, create a new
window. That window will be assigned the ID or name provided in the
commandline. The provided subcommands will be run in that new window.
* If `window-id` is omitted, then obey the value of `windowingBehavior` when
determining which window to run the command in.
_Whenever_ `wt.exe` is started, it must _always_ pass the provided commandline
first to the monarch process for handling. This is important for glomming
scenarios (as noted above). The monarch will parse the commandline, determine
which window the commandline is destined for, then call `ExecuteCommandline` on
that peasant, who will then run the command.
#### Running commands in the current window:`wt --window 0`
If `wt -w 0 <commands>` is run _outside_ a WT instance, it could attempt to glom
onto _the most recent WT window_ instead. This seems more logical than something
like `wt --window last` or some other special value indicating "run this in the
MRU window".<sup>[[2]](#footnote-2)</sup>
That might be a simple, but **wrong**, implementation for "the current window".
If the peasants always raise an event when their window is focused, and the
monarch keeps track of the MRU order for peasants, then one could naively assume
that the execution of `wt -w 0 <commands>` would always return the window the
user was typing in, the current one. However, if someone were to do something
like `sleep 10 ; wt -w 0 <commands>`, then the user could easily focus another
WT window during the sleep, which would cause the MRU window to not be the same
as the window executing the command.
To solve this issue, we'll other than
attempting to use the `WT_SESSION` environment variable. If a `wt.exe` process
is spawned and that's in its environment variables, it could try and ask the
monarch for the peasant who's hosting the session corresponding to that GUID.
This is more of a theoretical solution than anything else.
In the past we've been reluctant to rely too heavily on `WT_SESSION`. However,
an environment variable does seem to be the only reliable way to be confident
where the window was created from. We could introduce another environment
variable instead - `WT_WINDOW_ID`. That would allow us to shortcut the session
ID lookup. However, I worry about exposing the window ID as an environment
variable. If we do that, users will inevitably use that instead of the `wt -0`
alias, which should take care of the work for them. Additionally, `WT_WINDOW_ID`
wouldn't update in the child processes as tabs are torn out of windows to create
new windows.
Both solutions are prone to the user changing the value of the variable to some
garbage value. If they do that, this lookup will most certainly not work as
expected. Using the session ID (a GUID) instead of the window ID (an int) makes
it less likely that they guess the ID of an existing instance.
#### Running commands in a new window:`wt --window -1` / `wt --window new`
If the user passes a negative number, or the reserved name `new` to the
`--window` parameter, then we will always create a new window for that
commandline, regardless of the value of `windowingBehavior`. This will allow
users to do something like `wt -w -1 new-tab` to _always_ create a new window.
#### `--window` in subcommands
The `--window` parameter is a setting to `wt.exe` itself, not to one of its
subcommands (like `new-tab` or `split-pane`). This means that all of the
subcommands in a particular `wt` commandline will all be handled by the same
session. For example, let us consider a user who wants to open a new tab in
window 2, and split a new pane in window 3, all at once. The user _cannot_ do
something like:
```cmd
wt -w 2 new-tab ; -w 3 split-pane
```
Instead, the user will need to separate the commands (by whatever their shell's
own command delimiter is) and run two different `wt.exe` instances:
```cmd
wt -w 2 new-tab & wt -w 3 split-pane
```
This is done to make the parsing of the subcommands easier, and for the internal
passing of arguments simpler. If the `--window` parameter were a part of each
subcommand, then each individual subcommand's parser would need to be
enlightened about that parameter, and then it would need to be possible for any
single part of the commandline to call out to another process. It would be
especially tricky then to coordinate the work being done across process here.
The source process would need some sort of way to wait for the other process to
notify the source that a particular subcommand completed, before allowing the
source to dispatch the next part of the commandline.
Overall, this is seen as unnecessarily complex, and dispatching whole sets of
commands as a simpler solution.
### Naming Windows
It's not user-friendly to rely on automatically generated, invisible numbers to
identify windows. There's not a great way of identifying which window is which.
The user would need to track the IDs in their head manually. Instead, we'll
allow the user to provide a string name for the window. This name can be used to
address a window in addition to the ID.
Names can be provided on the commandline, in the original commandline. For
example, `wt -w foo nt` would name the new window "foo". Names can also be set
with a new action, `NameWindow`<sup>[[3]](#footnote-3)</sup>. `name-window`
could also be used as a subcommand. For example, `wt -w 4 name-window bar` would
name window 4 "bar".
To keep identities mentally distinct, we will disallow names that are integers
(positive or negative). This will prevent users from renaming a window to `2`,
then having `wt -w 2` be ambiguous as to which window it refers to.
Names must also be unique. If a user attempts to set the name of the window to
an already-used name, we'll need to ignore the name change. We could also
display a "toast" or some other type of low-impact message to the user. That
message would have some text like: "Unable to rename window. Another window with
that name already exists".
The Terminal will reserve the name `new`. It will also reserve any names
starting with the character `_`. The user will not be allowed to set the window
name to any of these reserved names. Reserving `_*` allows us to add other
keywords in the future, without introducing a breaking change.
## UI/UX Design
### `windowingBehavior` details
The following list gives greater breakdown of the values of `windowingBehavior`,
and how they operate:
* `"windowingBehavior": "useExisting", "useExistingOnSameDesktop"`:
**Browser-like glomming**
- New instances open in the current window by default.
- `newWindow` opens a new window.
- Tabs can be torn out to create new windows.
- `wt -w -1` opens a new window.
* `"windowingBehavior": "useNew"`: No auto-glomming. This is **the current
behavior** of the Terminal.
- New instances open in new windows by default
- `newWindow` opens a new window
- Tabs can be torn out to create new windows.
- `wt -w -1` opens a new window.
We'll be changing the default behavior from `useNew` to
`useExistingOnSameDesktop`. This will be more consistent with other tabbed
applications.
## Concerns
<table>
<tr>
<td><strong>Accessibility</strong></td>
<td>
There is no expected accessibility impact from this feature. Each window will
handle UIA access as it normally does.
In the future, we could consider exposing the window IDs and/or names via UIA.
</td>
</tr>
<tr>
<td><strong>Security</strong></td>
<td>
Many security concerns have already be covered in greater detail in the parent
spec, [Process Model 2.0 Spec].
When attempting to instantiate the Monarch, COM will only return the object from
a server running at the same elevation level. We don't need to worry about
unelevated peasants connecting to the elevated Monarch, or vice-versa.
</td>
</tr>
<tr>
<td><strong>Reliability</strong></td>
<td>
We will need to be careful when working with objects hosted by another process.
Any work we do with it MUST be in a try/catch, because at _any_ time, the other
process could be killed. At any point, a window process could be killed. Both
the monarch and peasant code will need to be redundant to such a scenario, and
if the other process is killed, make sure to display an appropriate error and
either recover or exit gracefully.
In any and all these situations, we will want to try and be as verbose as
possible in the logging. This will make tracking which process had the error
occur easier.
</td>
</tr>
<tr>
<td><strong>Compatibility</strong></td>
<td>
We will be changing the default behavior of the Terminal to auto-glom to the
most-recently used window on the same desktop in the course of this work, which
will be a breaking UX change. This is behavior that can be reverted with the
`"windowingBehavior": "useNew"` setting.
We acknowledge that this is a pretty massive change to the default experience of
the Terminal. We're planning on doing some polling of users to determine which
behavior they want by default. Additionally, we'll be staging the rollout of
this feature, using the Preview builds of the Terminal. The release notes that
first include it will call extra attention to this feature. We'll ask that users
provide their feedback in a dedicated thread, so we have time to collect
opinions from users before rolling the change out to all users.
We may choose to only change the default to `useExistingOnSameDesktop` once tab
tear out is available, so users who are particularly unhappy about this change
can still tear out the tab (if they can't be bothered to change the setting).
</td>
</tr>
<tr>
<td><strong>Performance, Power, and Efficiency</strong></td>
<td>
There's no dramatic change expected here. There may be a minor delay in the
spawning of new terminal instances, due to requiring cross-process hops for the
communication between monarch and peasant processes.
</td>
</tr>
</table>
## Potential Issues
### Mixed Elevation Levels
As of December 2020, we're no longer pursuing a "mixed-elevation" scenario for
the Terminal. This makes many of the cross-elevation scenarios simpler. Elevated
and unelevated `wt` instances will always remain separate. The different
elevation levels will maintain separate lists of window IDs. If the user is
running both an elevated and unelevated window, then there will be two monarchs.
One elevated, and the other unelevated.
There will also be some edge cases when handling the commandline that will need
special care. Say the user wanted to open a new tab in the elevated window, from
and unelevated `explorer.exe`. That would be a commandline like:
```sh
wt -w 0 new-tab -d . --elevated
```
Typically we first determine which window the commandline is intended for, then
dispatch it to that window. In this case, the `-w 0` will cause us to pass the
commandline to the current unelevated window. Then, that window will try to open
an elevated tab, fail, and create a new `wt.exe` process. This second `wt.exe`
process will lose the `-w 0` context. It won't inform the elevated monarch that
this commandline should be run in the active session.
We will need to make sure that special care is taken when creating elevated
instances that we maintain the `--window` parameter passed to the Terminal.
### `wt` Startup Commandline Options
There are a few commandline options which can be provided to `wt.exe` which
don't make sense to pass to another session. These options include (but are not
limited to):
* `--initialSize r,c`
* `--initialPosition x,y`
* `--fullscreen`, `--maximized`, etc.
When we're passing a commandline to another instance to handle, these arguments
will be ignored. they only apply to the initial creation of a window.
`--initialSize 32, 120` doesn't make sense if the window already has a size.
On startup of a new window, we currently assume that the first command is always
`new-tab`. When passing commandlines to existing windows, we won't need to make
that assumption anymore. There will already be existing tabs.
### Monarch MRU Window Tracking
As stated above, the monarch is responsible for tracking the MRU window stack.
However, when the monarch is closed, this state will be lost. The new monarch
will be elected, but it will be unable to ask the old monarch for the MRU
order of the windows.
We had previously considered an _acceptable_ UX when this would occur. We would
randomize the order (with the new monarch becoming the MRU window). If someone
noticed this bug and complained, then we had a theoretical solution prepared.
The peasants could inform not only the monarch, but _all other peasants_ when
they become activated. This would mean all peasants are simultaneously tracking
the MRU stack. This would mean that any given peasant would be prepared always
to become the monarch.
A simpler solution though would be to not track the MRU stack in the Monarch at
all. Instead, each peasant could just track internally when they were last
activated. The Monarch wouldn't track any state itself. It would be distributed
across all the peasants. The Monarch could then iterate over the list of
peasants and find the one with the newest `LastActivated` timestamp.
Now, when a Monarch dies, the new Peasant doesn't have to come up with the stack
itself. All the other Peasants keep their state. The new Monarch can query them
and get the same answer the old Monarch would have.
We could further optimize this by having the Monarch also track the stack. Then,
the monarch could query the MRU window quickly. The `LastActivated` timestamps
would only be used by a new Monarch when it is elected, to reconstruct the MRU
stack.
## Implementation Plan
This is a list of actionable tasks generated as described by this spec:
* [ ] Add support for `wt.exe` processes to be Monarchs and Peasants, and
communicate that state between themselves. This task does not otherwise add
any user-facing features, merely an architectural update.
* [ ] Add support for the `windowingBehavior` setting as a boolean. Opening new
WT windows will conditionally glom to existing windows.
* [ ] Add support for per-desktop `windowingBehavior`, by adding the support for
the enum values `"useExisting"`, `"useExistingOnSameDesktop"` and `"useNew"`.
* [ ] Add support for `wt.exe` to pass commandlines intended for another window
to the monarch, then to the intended window, with the `--window,-w
window-id` commandline parameter.
* [ ] Add support for targeting and naming windows via the `-w` parameter on the
commandline
* [ ] Add a `NameWindow` action, subcommand that allows the user to set the name
for the window.
* [ ] Add an action that will cause all windows to briefly display a overlay
with the current window ID and name. This would be something like the
"identify" feature of the Windows "Display" settings.
## Future considerations
* What if the user wanted to pipe a command to a pane in an existing window?
```sh
man ping > wt -w 0 split-pane cat
```
Is there some way for WT to pass its stdin/out handles to the child process
it's creating? This is _not_ related to the current spec at hand, just
something the author considered while writing the spec. This likely belongs
over in [#492], or in its own spec.
- Or I suppose, with less confusion, someone could run `wt -w 0 split-pane --
man ping > cat`. That's certainly more sensible, and wouldn't require any
extra work.
* "Single Instance Mode" is a scenario in which there is only ever one single WT
window. A user might want this functionality to only ever allow a single
terminal window to be open on their desktop. This is especially frequently
requested in combination with "quake mode", as discussed in [#653]. When Single
Instance Mode is active, and the user runs a new `wt.exe` commandline, it will
always end up running in the existing window, if there is one.
An earlier version of this spec proposed a new value of `glomToLastWindow`.
(`glomToLastWindow` was later renamed `windowingBehavior`). The `always` value
would disable tab tear out<sup>[[1]](#footnote-1)</sup>. It would additionally
disable the `newWindow` action, and prevent `wt -w new` from opening a new
window.
In discussion, it was concluded that this setting didn't make sense. Why did the
`glomToLastWindow` setting change the behavior of tear out? Single Instance Mode
is most frequently requested in regards to quake mode. We're leaving the
implementation of true single instance mode to that spec.
* It was suggested in review that we could auto-generate names for windows, from
some list of words. Prior art could be the URLS for gfycat.com or
what3words.com, which use three random words. I believe `docker` also assigns
names from a random selection of `adjective`+`name`. This is an interesting
idea, and something that could be pursued in the future.
- This would be a massive pain to localize though, hence why this is left as
a future consideration.
* We will _need_ to provide a commandline tool to list windows and their IDs &
names. We're thinking a list of windows, their IDs, names, PIDs, and the title
of the window.
Currently we're stuck with `wt.exe` which is a GUI application, and cannot
print to the console. Our need is now fairly high for the ability to print
info to the console. To remedy this, we'll need to ship another helper exe as
a commandline tool for working with the terminal. The design for this is left
for the future.
## Footnotes
<a name="footnote-1"><a>[1]: While tear-out is a separate track of work from
session management in general, this setting could be implemented along with this
set of features, and later used to control tear out as well.
<a name="footnote-2"><a>[2]: Since we're reserving the keyword `new` to mean "a
new window", then we could also reserve `last` or `current` as an alias for "the
current window".
<a name="footnote-3"><a>[3]: We currently have two actions for renaming _tabs_
in the Terminal: `renameTab(name)`, and `openTabRenamer()`. We will likely
similarly need `nameWindow(name)` and `openWindowNamer()`. `openWindowNamer`
could display a dialog to allow the user to rename the current window at
runtime.
## Resources
* [Tab Tear-out in the community toolkit] - this document proved invaluable to
the background of tearing a tab out of an application to create a new window.
<!-- Footnotes -->
[#5000]: https://github.com/microsoft/terminal/issues/5000
[#1256]: https://github.com/microsoft/terminal/issues/1256
[#4472]: https://github.com/microsoft/terminal/issues/4472
[#2227]: https://github.com/microsoft/terminal/issues/2227
[#653]: https://github.com/microsoft/terminal/issues/653
[#1032]: https://github.com/microsoft/terminal/issues/1032
[#632]: https://github.com/microsoft/terminal/issues/632
[#492]: https://github.com/microsoft/terminal/issues/492
[#4000]: https://github.com/microsoft/terminal/issues/4000
[#7972]: https://github.com/microsoft/terminal/pull/7972
[#961]: https://github.com/microsoft/terminal/issues/961
[`30b8335`]: https://github.com/microsoft/terminal/commit/30b833547928d6dcbf88d49df0dbd5b3f6a7c879
[Tab Tear-out in the community toolkit]: https://github.com/windows-toolkit/Sample-TabView-TearOff
[Quake mode scenarios]: https://github.com/microsoft/terminal/issues/653#issuecomment-661370107
[`ISwapChainPanelNative2::SetSwapChainHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -0,0 +1,95 @@
---
author: Kayla Cinnamon @cinnamon-msft
created on: 2020-01-03
last updated: 2020-01-03
issue id: 597
---
# Tab Sizing
## Abstract
This spec outlines the tab sizing feature. This is an application-level feature that is not profile-specific (at least for now).
Global properties that encompass tab sizing:
* `tabWidthMode` (accepts pre-defined values for tab sizing behavior)
* `tabWidthMin` (can never be smaller than the icon width)
* `tabWidthMax` (can never be wider than the tab bar)
Acceptable values for `tabWidthMode`:
* [default] `equal` (all tabs are sized the same, regardless of tab title length)
* `titleLength` (width of tab contains entire tab title)
## Inspiration
Other browsers and terminals have varying tab width behavior, so we should give people options.
## Solution Design
`tabWidthMode` will be a global setting that will accept the following strings:
* `equal`
* All tabs are equal in width
* If the tab bar has filled, tabs will shrink as additional tabs are added
* Utilizes the `equal` setting from WinUI's TabView
* `titleLength`
* Tab width varies depending on title length
* Width of tab will fit the whole tab title
* Utilizes the `sizeToContent` setting from WinUI's TabView
In addition to `tabWidthMode`, the following global properties will also be available:
* `tabWidthMin`
* Accepts an integer
* Value correlates to the minimum amount of pixels the tab width can be
* If value is less than the width of the icon, the minimum width will be the width of the icon
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
* If not set, the tab will have the system-defined minimum width
* `tabWidthMax`
* Accepts an integer
* Value correlates to the maximum amount of pixels the tab width can be
* If value is less than the width of the icon, the minimum width will be the width of the icon
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
* If not set, the tab will have the system-defined maximum width
If `tabWidthMode` is set to `titleLength`, the tab widths will fall between the `tabWidthMin` and `tabWidthMax` values if they are set, depending on the length of the tab title.
If `tabWidthMode` isn't set, the default experience will be `equal`. Justification for the default experience is the results from this [twitter poll](https://twitter.com/cinnamon_msft/status/1203093459055210496).
## UI/UX Design
[This tweet](https://twitter.com/cinnamon_msft/status/1203094776117022720) displays how the `equal` and `titleLength` values behave for the `tabWidthMode` property.
## Capabilities
### Accessibility
This feature could impact accessibility if the tab title isn't stored within the metadata of the tab. If the tab width is the width of the icon, then the title isn't visible. The tab title will have to be accessible by a screen reader.
### Security
This feature will not impact security.
### Reliability
This feature will not impact reliability. It provides users with additional customization options.
### Compatibility
This feature will not break existing compatibility.
### Performance, Power, and Efficiency
## Potential Issues
This feature will not impact performance, power, nor efficiency.
## Future considerations
* Provide tab sizing options per-profile
* A `tabWidthMode` value that will evenly divide the entirety of the tab bar by the number of open tabs
* i.e. One tab will take the full width of the tab bar, two tabs will each take up half the width of the tab bar, etc.

View File

@@ -2,17 +2,16 @@
## Overview
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Spring 2021.
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Winter 2021.
## Milestones
The Windows Terminal project is engineered and delivered as a set of 4-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they've been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
Windows Terminal is engineered and delivered as a set of 6-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they've been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
| Duration | Activity | Releases |
| --- | --- | --- |
| 2 weeks | Dev Work<br/> <ul><li>Fixes / Features for future Windows Releases</li><li>Fixes / Features for Windows Terminal</li></ul> | Release to Internal Selfhosters at end of week 2 |
| 1 week | Quality & Stability<br/> <ul><li>Bug Fixes</li><li>Perf & Stability</li><li>UI Polish</li><li>Tests</li><li>etc.</li></ul>| Push to Microsoft Store at end of week 3 |
| 4 weeks | Dev Work<br/> <ul><li>Fixes / Features for future Windows Releases</li><li>Fixes / Features for Windows Terminal</li></ul> | Release to Internal Selfhosters at end of week 4 |
| 1 week | Quality & Stability<br/> <ul><li>Bug Fixes</li><li>Perf & Stability</li><li>UI Polish</li><li>Tests</li><li>etc.</li></ul>| Push to Microsoft Store at end of week 5 |
| 1 week | Release <br/> <ul><li>Available from [Microsoft Store](https://aka.ms/terminal) & [GitHub Releases](https://github.com/microsoft/terminal/releases)</li><li>Release Notes & Announcement Blog published</li><li>Engineering System Maintenance</li><li>Community Engagement</li><li>Docs</li><li>Future Milestone Planning</li></ul> | Release available from Microsoft Store & GitHub Releases |
## Terminal Roadmap / Timeline
@@ -26,12 +25,15 @@ Below is the schedule for when milestones will be included in release builds of
| 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 | [Windows Terminal Preview 1.5 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-5-release/) |
| 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 | |
| 2021-01-31 | [1.6] in Windows Terminal Preview<br>[1.5] in Windows Terminal | [Windows Terminal Preview 1.6 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-6-release/) |
| 2021-03-01 | [1.7] in Windows Terminal Preview<br>[1.6] in Windows Terminal | [Windows Terminal Preview 1.7 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-7-release/) |
| 2021-04-30 | [1.8] in Windows Terminal Preview<br>[1.7] in Windows Terminal | |
| 2021-05-31 | [1.9] in Windows Terminal Preview<br>[1.8] in Windows Terminal | |
| 2021-07-31 | 1.10 in Windows Terminal Preview<br>[1.9] in Windows Terminal | |
| 2021-08-30 | 1.11 in Windows Terminal Preview<br>1.10 in Windows Terminal | |
| 2021-10-31 | 1.12 in Windows Terminal Preview<br>1.11 in Windows Terminal | |
| 2021-11-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
| 2021-12-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
## Issue Triage & Prioritization
@@ -84,6 +86,9 @@ Feature Notes:
[1.4]: https://github.com/microsoft/terminal/milestone/28
[1.5]: https://github.com/microsoft/terminal/milestone/30
[1.6]: https://github.com/microsoft/terminal/milestone/31
[1.7]: https://github.com/microsoft/terminal/milestone/32
[1.8]: https://github.com/microsoft/terminal/milestone/33
[1.9]: https://github.com/microsoft/terminal/milestone/34
[2.0]: https://github.com/microsoft/terminal/milestone/22
[#1564]: https://github.com/microsoft/terminal/issues/1564
[#6720]: https://github.com/microsoft/terminal/pull/6720

View File

@@ -4,7 +4,7 @@
"type": "git",
"git": {
"repositoryUrl": "https://github.com/fmtlib/fmt",
"commitHash": "f19b1a521ee8b606dedcadfda69fd10ddf882753"
"commitHash": "7bdf0628b1276379886c7f6dda2cef2b3b374f0b"
}
}
}

View File

@@ -72,43 +72,27 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
static_assert(F::is_integer, "From must be integral");
static_assert(T::is_integer, "To must be integral");
if (F::is_signed && !T::is_signed) {
if (detail::const_check(F::is_signed && !T::is_signed)) {
// From may be negative, not allowed!
if (fmt::detail::is_negative(from)) {
ec = 1;
return {};
}
// From is positive. Can it always fit in To?
if (F::digits <= T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
ec = 1;
return {};
}
if (F::digits > T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
}
}
if (!F::is_signed && T::is_signed) {
// can from be held in To?
if (F::digits < T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
// outside range.
ec = 1;
return {};
}
}
if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
}
// reaching here means all is ok for lossless conversion.
return static_cast<To>(from);
} // function
return static_cast<To>(from); // Lossless conversion.
}
template <typename To, typename From,
FMT_ENABLE_IF(std::is_same<From, To>::value)>
@@ -190,11 +174,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// safe conversion to IntermediateRep
IntermediateRep count =
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
if (ec) {
return {};
}
if (ec) return {};
// multiply with Factor::num without overflow or underflow
if (Factor::num != 1) {
if (detail::const_check(Factor::num != 1)) {
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
if (count > max1) {
ec = 1;
@@ -209,17 +191,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
count *= Factor::num;
}
// this can't go wrong, right? den>0 is checked earlier.
if (Factor::den != 1) {
count /= Factor::den;
}
// convert to the to type, safely
using ToRep = typename To::rep;
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
if (ec) {
return {};
}
return To{tocount};
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
return ec ? To() : To(tocount);
}
/**
@@ -351,6 +325,11 @@ inline std::tm localtime(std::time_t time) {
return lt.tm_;
}
inline std::tm localtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return localtime(std::chrono::system_clock::to_time_t(time_point));
}
// Thread-safe replacement for std::gmtime
inline std::tm gmtime(std::time_t time) {
struct dispatcher {
@@ -387,6 +366,11 @@ inline std::tm gmtime(std::time_t time) {
return gt.tm_;
}
inline std::tm gmtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return gmtime(std::chrono::system_clock::to_time_t(time_point));
}
namespace detail {
inline size_t strftime(char* str, size_t count, const char* format,
const std::tm* time) {
@@ -399,6 +383,17 @@ inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
}
} // namespace detail
template <typename Char>
struct formatter<std::chrono::time_point<std::chrono::system_clock>, Char>
: formatter<std::tm, Char> {
template <typename FormatContext>
auto format(std::chrono::time_point<std::chrono::system_clock> val,
FormatContext& ctx) -> decltype(ctx.out()) {
std::tm time = localtime(val);
return formatter<std::tm, Char>::format(time, ctx);
}
};
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {

View File

@@ -463,16 +463,16 @@ template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
}
template <typename Char>
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color;
const char* end = begin + sizeof(data::reset_color) - 1;
buffer.append(begin, end);
}
template <typename Char>
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
@@ -496,7 +496,7 @@ void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<Char>> args) {
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
@@ -504,20 +504,22 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
}
/**
\rst
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
Example:
**Example**::
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
detail::check_format_string<Args...>(format_str);
using context = buffer_context<char_t<S>>;
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
vprint(f, ts, format_str,
fmt::make_args_checked<Args...>(format_str, args...));
}
/**
@@ -558,7 +560,42 @@ template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
fmt::make_args_checked<Args...>(format_str, args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename OutputIt, typename Char,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
OutputIt vformat_to(
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
detail::is_string<S>::value>
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@@ -368,7 +368,8 @@ template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get(const T& first, const Args&... rest) {
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
@@ -406,6 +407,19 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value);
}
};
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
@@ -430,7 +444,9 @@ template <typename Char, typename T, int N> struct spec_field {
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
basic_format_context<OutputIt, Char> ctx(out, {});
const auto& vargs =
make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(arg, ctx);
}
};
@@ -489,16 +505,17 @@ constexpr auto parse_tail(T head, S format_str) {
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos) {
size_t pos, int arg_id) {
str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str);
auto ctx = basic_format_parse_context<Char>(str, {}, arg_id + 1);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + (end - str.data()) + 1};
return {f, pos + (end - str.data()) + 1, ctx.next_arg_id()};
}
// Compiles a non-empty format string and returns the compiled representation
@@ -518,8 +535,8 @@ constexpr auto compile_format_string(S format_str) {
format_str);
} else if constexpr (str[POS + 1] == ':') {
using type = get_type<ID, Args>;
constexpr auto result = parse_specs<type>(str, POS + 2);
return parse_tail<Args, result.end, ID + 1>(
constexpr auto result = parse_specs<type>(str, POS + 2, ID);
return parse_tail<Args, result.end, result.next_arg_id>(
spec_field<char_type, type, ID>{result.fmt}, format_str);
} else {
return unknown_format();
@@ -530,8 +547,13 @@ constexpr auto compile_format_string(S format_str) {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
}
}
@@ -587,8 +609,7 @@ template <typename CompiledFormat, typename... Args,
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
basic_memory_buffer<Char> buffer;
detail::buffer<Char>& base = buffer;
cf.format(std::back_inserter(base), args...);
cf.format(detail::buffer_appender<Char>(buffer), args...);
return to_string(buffer);
}
@@ -608,8 +629,7 @@ template <typename CompiledFormat, typename... Args,
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
using context = buffer_context<Char>;
detail::buffer<Char>& base = buffer;
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
detail::cf::vformat_to<context>(detail::buffer_appender<Char>(buffer), cf,
make_format_args<context>(args...));
return to_string(buffer);
}
@@ -618,9 +638,13 @@ template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
constexpr basic_string_view<typename S::char_type> str = S();
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
return fmt::to_string(detail::first(args...));
#ifdef __cpp_if_constexpr
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr basic_string_view<typename S::char_type> str = S();
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
return fmt::to_string(detail::first(args...));
}
#endif
constexpr auto compiled = detail::compile<Args...>(S());
return format(compiled, std::forward<Args>(args)...);
}
@@ -643,18 +667,30 @@ OutputIt format_to(OutputIt out, const S&, const Args&... args) {
return format_to(out, compiled, args...);
}
template <
typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
detail::basic_compiled_format, CompiledFormat>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf,
const Args&... args) {
template <typename OutputIt, typename CompiledFormat, typename... Args>
auto format_to_n(OutputIt out, size_t n, const CompiledFormat& cf,
const Args&... args) ->
typename std::enable_if<
detail::is_output_iterator<OutputIt,
typename CompiledFormat::char_type>::value &&
std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value,
format_to_n_result<OutputIt>>::type {
auto it =
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
return {it.base(), it.count()};
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, const S&,
const Args&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), compiled,
args...);
return {it.base(), it.count()};
}
template <typename CompiledFormat, typename... Args>
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(detail::counting_iterator(), cf, args...).count();

View File

@@ -18,7 +18,7 @@
#include <vector>
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 70001
#define FMT_VERSION 70103
#ifdef __clang__
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
@@ -57,6 +57,7 @@
# define FMT_MSC_VER 0
# define FMT_SUPPRESS_MSC_WARNING(n)
#endif
#ifdef __has_feature
# define FMT_HAS_FEATURE(x) __has_feature(x)
#else
@@ -64,7 +65,7 @@
#endif
#if defined(__has_include) && !defined(__INTELLISENSE__) && \
!(FMT_ICC_VERSION && FMT_ICC_VERSION < 1600)
(!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600)
# define FMT_HAS_INCLUDE(x) __has_include(x)
#else
# define FMT_HAS_INCLUDE(x) 0
@@ -99,7 +100,7 @@
#endif
#ifndef FMT_OVERRIDE
# if FMT_HAS_FEATURE(cxx_override) || \
# if FMT_HAS_FEATURE(cxx_override_control) || \
(FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
# define FMT_OVERRIDE override
# else
@@ -152,7 +153,7 @@
# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
# define FMT_DEPRECATED [[deprecated]]
# else
# if defined(__GNUC__) || defined(__clang__)
# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
# define FMT_DEPRECATED __attribute__((deprecated))
# elif FMT_MSC_VER
# define FMT_DEPRECATED __declspec(deprecated)
@@ -177,9 +178,17 @@
# endif
#endif
#ifndef FMT_BEGIN_NAMESPACE
#ifndef FMT_USE_INLINE_NAMESPACES
# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
FMT_MSC_VER >= 1900
(FMT_MSC_VER >= 1900 && !_MANAGED)
# define FMT_USE_INLINE_NAMESPACES 1
# else
# define FMT_USE_INLINE_NAMESPACES 0
# endif
#endif
#ifndef FMT_BEGIN_NAMESPACE
# if FMT_USE_INLINE_NAMESPACES
# define FMT_INLINE_NAMESPACE inline namespace
# define FMT_END_NAMESPACE \
} \
@@ -269,8 +278,7 @@ struct monostate {};
namespace detail {
// A helper function to suppress bogus "conditional expression is constant"
// warnings.
// A helper function to suppress "conditional expression is constant" warnings.
template <typename T> constexpr T const_check(T value) { return value; }
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
@@ -299,7 +307,8 @@ template <typename T> struct std_string_view {};
#ifdef FMT_USE_INT128
// Do nothing.
#elif defined(__SIZEOF_INT128__) && !FMT_NVCC
#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \
!(FMT_CLANG_VERSION && FMT_MSC_VER)
# define FMT_USE_INT128 1
using int128_t = __int128_t;
using uint128_t = __uint128_t;
@@ -506,6 +515,18 @@ template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> {
using type = typename result::value_type;
};
// Reports a compile-time error if S is not a valid format string.
template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
FMT_INLINE void check_format_string(const S&) {
#ifdef FMT_ENFORCE_COMPILE_STRING
static_assert(is_compile_string<S>::value,
"FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
"FMT_STRING.");
#endif
}
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
void check_format_string(S);
struct error_handler {
constexpr error_handler() = default;
constexpr error_handler(const error_handler&) = default;
@@ -545,8 +566,9 @@ class basic_format_parse_context : private ErrorHandler {
using iterator = typename basic_string_view<Char>::iterator;
explicit constexpr basic_format_parse_context(
basic_string_view<Char> format_str, ErrorHandler eh = {})
: ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {}
basic_string_view<Char> format_str, ErrorHandler eh = {},
int next_arg_id = 0)
: ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {}
/**
Returns an iterator to the beginning of the format string range being
@@ -616,8 +638,24 @@ template <typename T, typename Context>
using has_formatter =
std::is_constructible<typename Context::template formatter_type<T>>;
// Checks whether T is a container with contiguous storage.
template <typename T> struct is_contiguous : std::false_type {};
template <typename Char>
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
namespace detail {
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
inline Container& get_container(std::back_insert_iterator<Container> it) {
using bi_iterator = std::back_insert_iterator<Container>;
struct accessor : bi_iterator {
accessor(bi_iterator iter) : bi_iterator(iter) {}
using bi_iterator::container;
};
return *accessor(it).container;
}
/**
\rst
A contiguous memory buffer with an optional growing ability. It is an internal
@@ -640,6 +678,8 @@ template <typename T> class buffer {
size_(sz),
capacity_(cap) {}
~buffer() = default;
/** Sets the buffer data and capacity. */
void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
ptr_ = buf_data;
@@ -655,7 +695,6 @@ template <typename T> class buffer {
buffer(const buffer&) = delete;
void operator=(const buffer&) = delete;
virtual ~buffer() = default;
T* begin() FMT_NOEXCEPT { return ptr_; }
T* end() FMT_NOEXCEPT { return ptr_ + size_; }
@@ -675,24 +714,26 @@ template <typename T> class buffer {
/** Returns a pointer to the buffer data. */
const T* data() const FMT_NOEXCEPT { return ptr_; }
/**
Resizes the buffer. If T is a POD type new elements may not be initialized.
*/
void resize(size_t new_size) {
reserve(new_size);
size_ = new_size;
}
/** Clears this buffer. */
void clear() { size_ = 0; }
/** Reserves space to store at least *capacity* elements. */
void reserve(size_t new_capacity) {
// Tries resizing the buffer to contain *count* elements. If T is a POD type
// the new elements may not be initialized.
void try_resize(size_t count) {
try_reserve(count);
size_ = count <= capacity_ ? count : capacity_;
}
// Tries increasing the buffer capacity to *new_capacity*. It can increase the
// capacity by a smaller amount than requested but guarantees there is space
// for at least one additional element either by increasing the capacity or by
// flushing the buffer if it is full.
void try_reserve(size_t new_capacity) {
if (new_capacity > capacity_) grow(new_capacity);
}
void push_back(const T& value) {
reserve(size_ + 1);
try_reserve(size_ + 1);
ptr_[size_++] = value;
}
@@ -705,32 +746,150 @@ template <typename T> class buffer {
}
};
// A container-backed buffer.
struct buffer_traits {
explicit buffer_traits(size_t) {}
size_t count() const { return 0; }
size_t limit(size_t size) { return size; }
};
class fixed_buffer_traits {
private:
size_t count_ = 0;
size_t limit_;
public:
explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
size_t count() const { return count_; }
size_t limit(size_t size) {
size_t n = limit_ > count_ ? limit_ - count_ : 0;
count_ += size;
return size < n ? size : n;
}
};
// A buffer that writes to an output iterator when flushed.
template <typename OutputIt, typename T, typename Traits = buffer_traits>
class iterator_buffer final : public Traits, public buffer<T> {
private:
OutputIt out_;
enum { buffer_size = 256 };
T data_[buffer_size];
protected:
void grow(size_t) final FMT_OVERRIDE {
if (this->size() == buffer_size) flush();
}
void flush();
public:
explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
: Traits(n),
buffer<T>(data_, 0, buffer_size),
out_(out) {}
~iterator_buffer() { flush(); }
OutputIt out() {
flush();
return out_;
}
size_t count() const { return Traits::count() + this->size(); }
};
template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
protected:
void grow(size_t) final FMT_OVERRIDE {}
public:
explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {}
T* out() { return &*this->end(); }
};
// A buffer that writes to a container with the contiguous storage.
template <typename Container>
class container_buffer : public buffer<typename Container::value_type> {
class iterator_buffer<std::back_insert_iterator<Container>,
enable_if_t<is_contiguous<Container>::value,
typename Container::value_type>>
final : public buffer<typename Container::value_type> {
private:
Container& container_;
protected:
void grow(size_t capacity) FMT_OVERRIDE {
void grow(size_t capacity) final FMT_OVERRIDE {
container_.resize(capacity);
this->set(&container_[0], capacity);
}
public:
explicit container_buffer(Container& c)
explicit iterator_buffer(Container& c)
: buffer<typename Container::value_type>(c.size()), container_(c) {}
explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0)
: iterator_buffer(get_container(out)) {}
std::back_insert_iterator<Container> out() {
return std::back_inserter(container_);
}
};
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
inline Container& get_container(std::back_insert_iterator<Container> it) {
using bi_iterator = std::back_insert_iterator<Container>;
struct accessor : bi_iterator {
accessor(bi_iterator iter) : bi_iterator(iter) {}
using bi_iterator::container;
};
return *accessor(it).container;
// A buffer that counts the number of code units written discarding the output.
template <typename T = char> class counting_buffer final : public buffer<T> {
private:
enum { buffer_size = 256 };
T data_[buffer_size];
size_t count_ = 0;
protected:
void grow(size_t) final FMT_OVERRIDE {
if (this->size() != buffer_size) return;
count_ += this->size();
this->clear();
}
public:
counting_buffer() : buffer<T>(data_, 0, buffer_size) {}
size_t count() { return count_ + this->size(); }
};
// An output iterator that appends to the buffer.
// It is used to reduce symbol sizes for the common case.
template <typename T>
class buffer_appender : public std::back_insert_iterator<buffer<T>> {
using base = std::back_insert_iterator<buffer<T>>;
public:
explicit buffer_appender(buffer<T>& buf) : base(buf) {}
buffer_appender(base it) : base(it) {}
buffer_appender& operator++() {
base::operator++();
return *this;
}
buffer_appender operator++(int) {
buffer_appender tmp = *this;
++*this;
return tmp;
}
};
// Maps an output iterator into a buffer.
template <typename T, typename OutputIt>
iterator_buffer<OutputIt, T> get_buffer(OutputIt);
template <typename T> buffer<T>& get_buffer(buffer_appender<T>);
template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) {
return out;
}
template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) {
return get_container(out);
}
template <typename Buffer>
auto get_iterator(Buffer& buf) -> decltype(buf.out()) {
return buf.out();
}
template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) {
return buffer_appender<T>(buf);
}
template <typename T, typename Char = char, typename Enable = void>
@@ -759,7 +918,8 @@ template <typename Char> struct named_arg_info {
template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
struct arg_data {
// args_[0].named_args points to named_args_ to avoid bloating format_args.
T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)];
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)];
named_arg_info<Char> named_args_[NUM_NAMED_ARGS];
template <typename... U>
@@ -771,7 +931,8 @@ struct arg_data {
template <typename T, typename Char, size_t NUM_ARGS>
struct arg_data<T, Char, NUM_ARGS, 0> {
T args_[NUM_ARGS != 0 ? NUM_ARGS : 1];
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];
template <typename... U>
FMT_INLINE arg_data(const U&... init) : args_{init...} {}
@@ -959,6 +1120,8 @@ enum { long_short = sizeof(long) == sizeof(int) };
using long_type = conditional_t<long_short, int, long long>;
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
struct unformattable {};
// Maps formatting arguments to core types.
template <typename Context> struct arg_mapper {
using char_type = typename Context::char_type;
@@ -1067,15 +1230,7 @@ template <typename Context> struct arg_mapper {
return map(val.value);
}
int map(...) {
constexpr bool formattable = sizeof(Context) == 0;
static_assert(
formattable,
"Cannot format argument. To make type T formattable provide a "
"formatter<T> specialization: "
"https://fmt.dev/latest/api.html#formatting-user-defined-types");
return 0;
}
unformattable map(...) { return {}; }
};
// A type constant after applying arg_mapper<Context>.
@@ -1199,15 +1354,25 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg(
return vis(monostate());
}
// Checks whether T is a container with contiguous storage.
template <typename T> struct is_contiguous : std::false_type {};
template <typename Char>
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
template <typename Char>
struct is_contiguous<detail::buffer<Char>> : std::true_type {};
template <typename T> struct formattable : std::false_type {};
namespace detail {
// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
template <typename... Ts> struct void_t_impl { using type = void; };
template <typename... Ts>
using void_t = typename detail::void_t_impl<Ts...>::type;
template <typename It, typename T, typename Enable = void>
struct is_output_iterator : std::false_type {};
template <typename It, typename T>
struct is_output_iterator<
It, T,
void_t<typename std::iterator_traits<It>::iterator_category,
decltype(*std::declval<It>() = std::declval<T>())>>
: std::true_type {};
template <typename OutputIt>
struct is_back_insert_iterator : std::false_type {};
template <typename Container>
@@ -1219,6 +1384,9 @@ struct is_contiguous_back_insert_iterator : std::false_type {};
template <typename Container>
struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
: is_contiguous<Container> {};
template <typename Char>
struct is_contiguous_back_insert_iterator<buffer_appender<Char>>
: std::true_type {};
// A type-erased reference to an std::locale to avoid heavy <locale> include.
class locale_ref {
@@ -1250,13 +1418,24 @@ FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value) {
return arg;
}
template <typename T> int check(unformattable) {
static_assert(
formattable<T>(),
"Cannot format an argument. To make type T formattable provide a "
"formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
return 0;
}
template <typename T, typename U> inline const U& check(const U& val) {
return val;
}
// The type template parameter is there to avoid an ODR violation when using
// a fallback formatter in one translation unit and an implicit conversion in
// another (not recommended).
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(IS_PACKED)>
inline value<Context> make_arg(const T& val) {
return arg_mapper<Context>().map(val);
return check<T>(arg_mapper<Context>().map(val));
}
template <bool IS_PACKED, typename Context, type, typename T,
@@ -1356,13 +1535,13 @@ template <typename OutputIt, typename Char> class basic_format_context {
template <typename Char>
using buffer_context =
basic_format_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
basic_format_context<detail::buffer_appender<Char>, Char>;
using format_context = buffer_context<char>;
using wformat_context = buffer_context<wchar_t>;
// Workaround a bug in gcc: https://stackoverflow.com/q/62767544/471164.
// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164.
#define FMT_BUFFER_CONTEXT(Char) \
basic_format_context<std::back_insert_iterator<detail::buffer<Char>>, Char>
basic_format_context<detail::buffer_appender<Char>, Char>
/**
\rst
@@ -1414,7 +1593,7 @@ class format_arg_store
/**
\rst
Constructs an `~fmt::format_arg_store` object that contains references to
Constructs a `~fmt::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmt::format_args`. `Context`
can be omitted in which case it defaults to `~fmt::context`.
See `~fmt::arg` for lifetime considerations.
@@ -1426,6 +1605,27 @@ inline format_arg_store<Context, Args...> make_format_args(
return {args...};
}
/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``format_str`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
inline auto make_args_checked(const S& format_str,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(format_str);
return {args...};
}
/**
\rst
Returns a named argument to be used in a formatting function. It should only
@@ -1713,7 +1913,7 @@ template <typename Context> class basic_format_args {
}
template <typename Char> int get_id(basic_string_view<Char> name) const {
if (!has_named_args()) return {};
if (!has_named_args()) return -1;
const auto& named_args =
(is_packed() ? values_[-1] : args_[-1].value_).named_args;
for (size_t i = 0; i < named_args.size; ++i) {
@@ -1729,7 +1929,14 @@ template <typename Context> class basic_format_args {
}
};
/** An alias to ``basic_format_args<context>``. */
#ifdef FMT_ARM_ABI_COMPATIBILITY
/** An alias to ``basic_format_args<format_context>``. */
// Separate types would result in shorter symbols but break ABI compatibility
// between clang and gcc on ARM (#1919).
using format_args = basic_format_args<format_context>;
using wformat_args = basic_format_args<wformat_context>;
#else
// DEPRECATED! These are kept for ABI compatibility.
// It is a separate type rather than an alias to make symbols readable.
struct format_args : basic_format_args<format_context> {
template <typename... Args>
@@ -1738,32 +1945,10 @@ struct format_args : basic_format_args<format_context> {
struct wformat_args : basic_format_args<wformat_context> {
using basic_format_args::basic_format_args;
};
#endif
namespace detail {
// Reports a compile-time error if S is not a valid format string.
template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
FMT_INLINE void check_format_string(const S&) {
#ifdef FMT_ENFORCE_COMPILE_STRING
static_assert(is_compile_string<S>::value,
"FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
"FMT_STRING.");
#endif
}
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
void check_format_string(S);
template <typename... Args, typename S, typename Char = char_t<S>>
inline format_arg_store<buffer_context<Char>, remove_reference_t<Args>...>
make_args_checked(const S& format_str,
const remove_reference_t<Args>&... args) {
static_assert(count<(std::is_base_of<view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
check_format_string<Args...>(format_str);
return {args...};
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
std::basic_string<Char> vformat(
basic_string_view<Char> format_str,
@@ -1772,9 +1957,10 @@ std::basic_string<Char> vformat(
FMT_API std::string vformat(string_view format_str, format_args args);
template <typename Char>
typename FMT_BUFFER_CONTEXT(Char)::iterator vformat_to(
void vformat_to(
buffer<Char>& buf, basic_string_view<Char> format_str,
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args);
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
detail::locale_ref loc = {});
template <typename Char, typename Args,
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
@@ -1789,26 +1975,80 @@ inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
/** Formats a string and writes the output to ``out``. */
// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.
template <
typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator<OutputIt>::value)>
OutputIt vformat_to(
OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto& c = detail::get_container(out);
detail::container_buffer<remove_reference_t<decltype(c)>> buf(c);
template <typename OutputIt, typename S, typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value>
auto vformat_to(OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> typename std::enable_if<enable, OutputIt>::type {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
detail::vformat_to(buf, to_string_view(format_str), args);
return out;
return detail::get_iterator(buf);
}
template <typename Container, typename S, typename... Args,
FMT_ENABLE_IF(
is_contiguous<Container>::value&& detail::is_string<S>::value)>
inline std::back_insert_iterator<Container> format_to(
std::back_insert_iterator<Container> out, const S& format_str,
Args&&... args) {
return vformat_to(out, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
/**
\rst
Formats arguments, writes the result to the output iterator ``out`` and returns
the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out), "{}", 42);
\endrst
*/
// We cannot use FMT_ENABLE_IF because of a bug in gcc 8.3.
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
inline auto format_to(OutputIt out, const S& format_str, Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, to_string_view(format_str), vargs);
}
template <typename OutputIt> struct format_to_n_result {
/** Iterator past the end of the output range. */
OutputIt out;
/** Total (not truncated) output size. */
size_t size;
};
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
inline format_to_n_result<OutputIt> vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
n);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
/**
\rst
Formats arguments, writes up to ``n`` characters of the result to the output
iterator ``out`` and returns the total output size and the iterator past the
end of the output range.
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
inline auto format_to_n(OutputIt out, size_t n, const S& format_str,
const Args&... args) ->
typename std::enable_if<enable, format_to_n_result<OutputIt>>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to_n(out, n, to_string_view(format_str), vargs);
}
/**
Returns the number of characters in the output of
``format(format_str, args...)``.
*/
template <typename... Args>
inline size_t formatted_size(string_view format_str, Args&&... args) {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
detail::counting_buffer<> buf;
detail::vformat_to(buf, format_str, vargs);
return buf.count();
}
template <typename S, typename Char = char_t<S>>
@@ -1832,7 +2072,7 @@ FMT_INLINE std::basic_string<Char> vformat(
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... Args, typename Char = char_t<S>>
FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) {
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return detail::vformat(to_string_view(format_str), vargs);
}
@@ -1852,7 +2092,7 @@ FMT_API void vprint(std::FILE*, string_view, format_args);
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline void print(std::FILE* f, const S& format_str, Args&&... args) {
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return detail::is_unicode<Char>()
? vprint(f, to_string_view(format_str), vargs)
: detail::vprint_mojibake(f, to_string_view(format_str), vargs);
@@ -1871,7 +2111,7 @@ inline void print(std::FILE* f, const S& format_str, Args&&... args) {
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline void print(const S& format_str, Args&&... args) {
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return detail::is_unicode<Char>()
? vprint(to_string_view(format_str), vargs)
: detail::vprint_mojibake(stdout, to_string_view(format_str),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,22 +15,12 @@
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char>
typename buffer_context<Char>::iterator vformat_to(
const std::locale& loc, buffer<Char>& buf,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
args, detail::locale_ref(loc));
}
template <typename Char>
std::basic_string<Char> vformat(
const std::locale& loc, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
detail::vformat_to(loc, buffer, format_str, args);
detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc));
return fmt::to_string(buffer);
}
} // namespace detail
@@ -45,32 +35,28 @@ inline std::basic_string<Char> vformat(
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const std::locale& loc,
const S& format_str, Args&&... args) {
return detail::vformat(
loc, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
return detail::vformat(loc, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
template <typename S, typename OutputIt, typename... Args,
typename Char = enable_if_t<
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
inline OutputIt vformat_to(
OutputIt out, const std::locale& loc, const S& format_str,
format_args_t<type_identity_t<OutputIt>, Char> args) {
using af = detail::arg_formatter<OutputIt, Char>;
return vformat_to<af>(out, to_string_view(format_str), args,
detail::locale_ref(loc));
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
return detail::get_iterator(buf);
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) {
detail::check_format_string<Args...>(format_str);
using context = format_context_t<OutputIt, char_t<S>>;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),
basic_format_args<context>(as));
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
inline auto format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, loc, to_string_view(format_str), vargs);
}
FMT_END_NAMESPACE

View File

@@ -29,7 +29,8 @@
#if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
#endif
#if FMT_HAS_INCLUDE("fcntl.h") && \
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
@@ -278,7 +279,8 @@ class file {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMT_POSIX(O_APPEND) // Open in append mode.
};
// Constructs a file object which doesn't represent any file.
@@ -343,36 +345,69 @@ class file {
// Returns the memory page size.
long getpagesize();
class direct_buffered_file;
namespace detail {
template <typename S, typename... Args>
void print(direct_buffered_file& f, const S& format_str,
const Args&... args);
struct buffer_size {
size_t value = 0;
buffer_size operator=(size_t val) const {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
// A buffered file with a direct buffer access and no synchronization.
class direct_buffered_file {
struct ostream_params {
int oflag = file::WRONLY | file::CREATE;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int oflag) : ostream_params(params...) {
this->oflag = oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
};
} // namespace detail
static constexpr detail::buffer_size buffer_size;
// A fast output stream which is not thread-safe.
class ostream final : private detail::buffer<char> {
private:
file file_;
enum { buffer_size = 4096 };
char buffer_[buffer_size];
int pos_;
void flush() {
if (pos_ == 0) return;
file_.write(buffer_, pos_);
pos_ = 0;
if (size() == 0) return;
file_.write(data(), size());
clear();
}
int free_capacity() const { return buffer_size - pos_; }
FMT_API void grow(size_t) override final;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
public:
direct_buffered_file(cstring_view path, int oflag)
: file_(path, oflag), pos_(0) {}
~direct_buffered_file() {
flush();
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() {
flush();
@@ -380,25 +415,20 @@ class direct_buffered_file {
}
template <typename S, typename... Args>
friend void print(direct_buffered_file& f, const S& format_str,
const Args&... args) {
// We could avoid double buffering.
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf), format_str, args...);
auto remaining_pos = 0;
auto remaining_size = buf.size();
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
auto size = f.free_capacity();
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
f.pos_ += size;
f.flush();
remaining_pos += size;
remaining_size -= size;
}
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
f.pos_ += static_cast<int>(remaining_size);
void print(const S& format_str, const Args&... args) {
format_to(detail::buffer_appender<char>(*this), format_str, args...);
}
};
/**
Opens a file for writing. Supported parameters passed in `params`:
* ``<integer>``: Output flags (``file::WRONLY | file::CREATE`` by default)
* ``buffer_size=<integer>``: Output buffer size
*/
template <typename... T>
inline ostream output_file(cstring_view path, T... params) {
return {path, detail::ostream_params(params...)};
}
#endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE

View File

@@ -49,17 +49,27 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
}
};
struct converter {
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
};
template <typename Char> struct test_stream : std::basic_ostream<Char> {
private:
// Hide all operator<< from std::basic_ostream<Char>.
void_t<> operator<<(null<>);
void_t<> operator<<(const Char*);
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
!std::is_enum<T>::value)>
void_t<> operator<<(T);
void_t<> operator<<(converter);
};
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
template <typename T, typename Char> class is_streamable {
@@ -103,7 +113,7 @@ void format_value(buffer<Char>& buf, const T& value,
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.resize(buf.size());
buf.try_resize(buf.size());
}
// Formats an object of type T that has an overloaded ostream operator<<.
@@ -160,7 +170,7 @@ template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
vprint(os, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
fmt::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@@ -181,7 +181,7 @@ template <typename Char> class printf_width_handler {
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format();
Context(buffer_appender<Char>(buf), format, args).format();
}
} // namespace detail
@@ -598,7 +598,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
template <typename Char>
using basic_printf_context_t =
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
basic_printf_context<detail::buffer_appender<Char>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;

View File

@@ -157,6 +157,9 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
}
template <typename Range>
using value_type = remove_cvref_t<decltype(*std::declval<Range>().begin())>;
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
typename std::decay<Arg>::type>::value)>
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
@@ -182,7 +185,6 @@ FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
return add_space ? L" '{}'" : L"'{}'";
}
} // namespace detail
template <typename T> struct is_tuple_like {
@@ -246,9 +248,18 @@ template <typename T, typename Char> struct is_range {
!std::is_constructible<detail::std_string_view<Char>, T>::value;
};
template <typename RangeT, typename Char>
struct formatter<RangeT, Char,
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2017 and earlier.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
&&
(has_formatter<detail::value_type<T>, format_context>::value ||
detail::has_fallback_formatter<detail::value_type<T>,
format_context>::value)
#endif
>> {
formatting_range<Char> formatting;
template <typename ParseContext>
@@ -257,8 +268,7 @@ struct formatter<RangeT, Char,
}
template <typename FormatContext>
typename FormatContext::iterator format(const RangeT& values,
FormatContext& ctx) {
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
auto out = detail::copy(formatting.prefix, ctx.out());
size_t i = 0;
auto it = values.begin();

View File

@@ -23,6 +23,36 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
FMT_NOEXCEPT;
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
FMT_NOEXCEPT;
// DEPRECATED! This function exists for ABI compatibility.
template <typename Char>
typename basic_format_context<std::back_insert_iterator<buffer<Char>>,
Char>::iterator
vformat_to(buffer<Char>& buf, basic_string_view<Char> format_str,
basic_format_args<basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
type_identity_t<Char>>>
args) {
using iterator = std::back_insert_iterator<buffer<char>>;
using context = basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
type_identity_t<Char>>;
auto out = iterator(buf);
format_handler<iterator, Char, context> h(out, format_str, args, {});
parse_format_string<false>(format_str, h);
return out;
}
template basic_format_context<std::back_insert_iterator<buffer<char>>,
char>::iterator
vformat_to(buffer<char>&, string_view,
basic_format_args<basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<char>>>,
type_identity_t<char>>>);
} // namespace detail
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
@@ -44,9 +74,9 @@ template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<char>::append(const char*, const char*);
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
template FMT_API void detail::vformat_to(
detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
detail::buffer<char>&);

View File

@@ -62,7 +62,7 @@ using RWResult = int;
inline unsigned convert_rwcount(std::size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
}
#else
#elif FMT_USE_FCNTL
// Return type of read and write functions.
using RWResult = ssize_t;
@@ -124,7 +124,8 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
if (result != 0) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
format_to(buffer_appender<char>(out), "{}: {}", message,
utf8_message);
return;
}
break;
@@ -288,12 +289,12 @@ void file::pipe(file& read_end, file& write_end) {
}
buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR.
#if defined(__MINGW32__) && defined(_POSIX_)
// Don't retry as fdopen doesn't return EINTR.
# if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode);
#else
# else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
#endif
# endif
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));
@@ -313,5 +314,9 @@ long getpagesize() {
return size;
# endif
}
FMT_API void ostream::grow(size_t) {
if (this->size() == this->capacity()) flush();
}
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 13H16V6H2C0.9 6 0 6.9 0 8V13Z" fill="#CCCCCC"/>
<path d="M32 6H16V13H32V6Z" fill="#999999"/>

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="foreground"><stop stop-color="#000000"/></linearGradient>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="foreground"><stop stop-color="#000000"/></linearGradient>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,42 @@
// A simple animated shader that fades the terminal background back and forth between two colours
// The terminal graphics as a texture
Texture2D shaderTexture;
SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings
{
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
// Resolution of the shaderTexture
float2 Resolution;
// Background color as rgba
float4 Background;
};
// pi and tau (2 * pi) are useful constants when using trigonometric functions
#define TAU 6.28318530718
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 sample = shaderTexture.Sample(samplerState, tex);
// The number of seconds the breathing effect should span
float duration = 5.0;
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
// Set background colour based on the time
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
// Draw the terminal graphics over the background
return lerp(backgroundColor, sample, sample.w);
}

View File

@@ -0,0 +1,45 @@
// A simple animated shader that draws an inverted line that scrolls down the screen
// The terminal graphics as a texture
Texture2D shaderTexture;
SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings
{
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
// Resolution of the shaderTexture
float2 Resolution;
// Background color as rgba
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 color = shaderTexture.Sample(samplerState, tex);
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
// the timer to count to five repeatedly. We then divide the result by five again
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
float linePosition = Time % 5 / 5;
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
// texture to find out the size of a single pixel
float lineWidth = 1.0 / Resolution.y;
// If the current texture coordinate is in the range of our line on the Y axis:
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
{
// Invert the sampled color
color.rgb = 1.0 - color.rgb;
}
return color;
}

View File

@@ -6,7 +6,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// Time since pixel shader was enabled
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)

View File

@@ -6,7 +6,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// Time since pixel shader was enabled
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)

View File

@@ -20,7 +20,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// Time since pixel shader was enabled
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -30,8 +30,9 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
@@ -49,16 +50,22 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
Save this file as `C:\temp\invert.hlsl`, then update a profile with the setting:
```
"experimental.pixelShaderEffect": "C:\\temp\\invert.hlsl"
"experimental.pixelShaderPath": "C:\\temp\\invert.hlsl"
```
Once the settings file is saved, open a terminal with the changed profile. It should now invert the colors of the screen!
Default Terminal | Inverted Terminal
---------|---------
![Default Terminal](Screenshots/TerminalDefault.PNG) | ![Inverted Terminal](Screenshots/TerminalInvert.PNG)
If your shader fails to compile, the Terminal will display a warning dialog and ignore it temporarily. After fixing your shader, touch the `settings.json` file again, or open a new tab, and the Terminal will try loading the shader again.
## HLSL
The language we use to write pixel shaders is called `HLSL`. It a `C`-like language, with some restrictions.You can't allocate memory, use pointers or recursion.
The language we use to write pixel shaders is called `HLSL`. It's a `C`-like language, with some restrictions. You can't allocate memory, use pointers or recursion.
What you get access to is computing power in the teraflop range on decently recent GPUs. This means writing real-time raytracers or other cool effects are in the realm of possibility.
[shadertoy](https://shadertoy.com/) is a great site that show case what's possible with pixel shaders (albeit in `GLSL`). For example this [menger sponge](https://www.shadertoy.com/view/4scXzn). Converting from `GLSL` to `HLSL` isn't overly hard once you gotten the hang of it.
@@ -76,7 +83,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// Time since pixel shader was enabled
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -86,8 +93,9 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
@@ -130,7 +138,83 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
Once reloaded, it should show some retro raster bars in the background, with a drop shadow to make the text more readable.
![Rasterbars](Screenshots/TerminalRasterbars.PNG)
## Retro Terminal Effect
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory. Feel free to modify and experiment!
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory.
![Retro](Screenshots/TerminalRetro.PNG)
## Animated Effects
You can use the `Time` value in the shader input settings to drive animated effects. `Time` is the number of seconds since the shader first loaded. Heres a simple example with a line of inverted pixels that scrolls down the terminal (`Animate_scan.hlsl`):
```hlsl
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 color = shaderTexture.Sample(samplerState, tex);
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
// the timer to count to five repeatedly. We then divide the result by five again
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
float linePosition = Time % 5 / 5;
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
// texture to find out the size of a single pixel
float lineWidth = 1.0 / Resolution.y;
// If the current texture coordinate is in the range of our line on the Y axis:
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
{
// Invert the sampled color
color.rgb = 1.0 - color.rgb;
}
return color;
}
```
What if we want an animation that goes backwards and forwards? In this example (`Animate_breathe.hlsl`), we'll make the background fade between two colours. Our `Time` value only ever goes up, so we need a way to generate a value that sweeps back and forth from `0.0` to `1.0`. Trigonometric functions like cosine are perfect for this and are very frequently used in shaders.
`cos()` outputs a value between `-1.0` and `1.0`. We can adjust the wave with the following formula:
```
a * cos(b * (x - c)) + d
```
Where `a` adjusts the amplitude, `b` adjusts the wavelength/frequency, `c` adjusts the offset along the x axis, and `d` adjusts the offset along the y axis. You can use a graphing calculator (such as the Windows Calculator) to help visualize the output and experiment:
![Cosine](Screenshots/GraphCosine.png)
As shown above, by halving the output and then adding `0.5`, we can shift the range of the function to `0.0` - `1.0`. Because `cos()` takes input in radians, if we multiply `x` (`Time`) by tau (`2*pi`), we are effectively setting the wavelength to `1.0`.
In other words, our full animation will be one second long. We can modify this duration by dividing tau by the number of seconds we want the animation to run for. In this case, well go for five seconds.
Finally we use linear interpolation to achieve our breathing effect by selecting a color between our two chosen colors based on the output from our cosine.
```hlsl
// pi and tau (2 * pi) are useful constants when using trigonometric functions
#define TAU 6.28318530718
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 sample = shaderTexture.Sample(samplerState, tex);
// The number of seconds the breathing effect should span
float duration = 5.0;
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
// Set background colour based on the time
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
// Draw the terminal graphics over the background
return lerp(backgroundColor, sample, sample.w);
}
```
Feel free to modify and experiment!

View File

@@ -6,7 +6,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// Time since pixel shader was enabled
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

View File

@@ -4,30 +4,6 @@
#include "precomp.h"
#include "AttrRow.hpp"
// Routine Description:
// - constructor
// Arguments:
// - cchRowWidth - the length of the default text attribute
// - attr - the default text attribute
// Return Value:
// - constructed object
// Note: will throw exception if unable to allocate memory for text attribute storage
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
{
_list.push_back(TextAttributeRun(cchRowWidth, attr));
_cchRowWidth = cchRowWidth;
}
// Routine Description:
// - Sets all properties of the ATTR_ROW to default values
// Arguments:
// - attr - The default text attributes to use on text in this row.
void ATTR_ROW::Reset(const TextAttribute attr)
{
_list.clear();
_list.push_back(TextAttributeRun(_cchRowWidth, attr));
}
// Routine Description:
// - Takes an existing row of attributes, and changes the length so that it fills the NewWidth.
// If the new size is bigger, then the last attr is extended to fill the NewWidth.
@@ -39,46 +15,12 @@ void ATTR_ROW::Reset(const TextAttribute attr)
// - <none>, throws exceptions on failures.
void ATTR_ROW::Resize(const size_t newWidth)
{
THROW_HR_IF(E_INVALIDARG, 0 == newWidth);
mybase::resize(gsl::narrow<UINT>(newWidth));
}
// Easy case. If the new row is longer, increase the length of the last run by how much new space there is.
if (newWidth > _cchRowWidth)
{
// Get the attribute that covers the final column of old width.
const auto runPos = FindAttrIndex(_cchRowWidth - 1, nullptr);
auto& run = _list.at(runPos);
// Extend its length by the additional columns we're adding.
run.SetLength(run.GetLength() + newWidth - _cchRowWidth);
// Store that the new total width we represent is the new width.
_cchRowWidth = newWidth;
}
// harder case: new row is shorter.
else
{
// Get the attribute that covers the final column of the new width
size_t CountOfAttr = 0;
const auto runPos = FindAttrIndex(newWidth - 1, &CountOfAttr);
auto& run = _list.at(runPos);
// CountOfAttr was given to us as "how many columns left from this point forward are covered by the returned run"
// So if the original run was B5 covering a 5 size OldWidth and we have a NewWidth of 3
// then when we called FindAttrIndex, it returned the B5 as the pIndexedRun and a 2 for how many more segments it covers
// after and including the 3rd column.
// B5-2 = B3, which is what we desire to cover the new 3 size buffer.
run.SetLength(run.GetLength() - CountOfAttr + 1);
// Store that the new total width we represent is the new width.
_cchRowWidth = newWidth;
// Erase segments after the one we just updated.
_list.erase(_list.cbegin() + runPos + 1, _list.cend());
// NOTE: Under some circumstances here, we have leftover run segments in memory or blank run segments
// in memory. We're not going to waste time redimensioning the array in the heap. We're just noting that the useful
// portions of it have changed.
}
void ATTR_ROW::Reset(const TextAttribute attr)
{
mybase::fill(attr);
}
// Routine Description:
@@ -91,7 +33,7 @@ void ATTR_ROW::Resize(const size_t newWidth)
// - will throw on error
TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column) const
{
return GetAttrByColumn(column, nullptr);
return mybase::at(gsl::narrow<UINT>(column));
}
// Routine Description:
@@ -106,87 +48,24 @@ TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column) const
TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column,
size_t* const pApplies) const
{
THROW_HR_IF(E_INVALIDARG, column >= _cchRowWidth);
const auto runPos = FindAttrIndex(column, pApplies);
return _list.at(runPos).GetAttributes();
}
// Routine Description:
// - reports how many runs we have stored (to be used for some optimizations
// Return Value:
// - Count of runs. 1 means we have 1 color to represent the entire row.
size_t ATTR_ROW::GetNumberOfRuns() const noexcept
{
return _list.size();
}
// Routine Description:
// - This routine finds the nth attribute in this ATTR_ROW.
// Arguments:
// - index - which attribute to find
// - applies - on output, contains corrected length of indexed attr.
// for example, if the attribute string was { 5, BLUE } and the requested
// index was 3, CountOfAttr would be 2.
// Return Value:
// - const reference to attribute run object
size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
{
FAIL_FAST_IF(!(index < _cchRowWidth)); // The requested index cannot be longer than the total length described by this set of Attrs.
size_t cTotalLength = 0;
FAIL_FAST_IF(!(_list.size() > 0)); // There should be a non-zero and positive number of items in the array.
// Scan through the internal array from position 0 adding up the lengths that each attribute applies to
auto runPos = _list.cbegin();
do
{
cTotalLength += runPos->GetLength();
if (cTotalLength > index)
{
// If we've just passed up the requested index with the length we added, break early
break;
}
runPos++;
} while (runPos < _list.cend());
// we should have broken before falling out the while case.
// if we didn't break, then this ATTR_ROW wasn't filled with enough attributes for the entire row of characters
FAIL_FAST_IF(runPos >= _list.cend());
// The remaining iterator position is the position of the attribute that is applicable at the position requested (index)
// Calculate its remaining applicability if requested
// The length on which the found attribute applies is the total length seen so far minus the index we were searching for.
FAIL_FAST_IF(!(cTotalLength > index)); // The length of all attributes we counted up so far should be longer than the index requested or we'll underflow.
if (nullptr != pApplies)
{
const auto attrApplies = cTotalLength - index;
FAIL_FAST_IF(!(attrApplies > 0)); // An attribute applies for >0 characters
// MSFT: 17130145 - will restore this and add a better assert to catch the real issue.
//FAIL_FAST_IF(!(attrApplies <= _cchRowWidth)); // An attribute applies for a maximum of the total length available to us
*pApplies = attrApplies;
}
return runPos - _list.cbegin();
UINT applies = 0;
const auto attr = mybase::at(gsl::narrow<UINT>(column), applies);
*pApplies = applies;
return attr;
}
// Routine Description:
// - Finds the hyperlink IDs present in this row and returns them
// Return value:
// - An unordered set containing the hyperlink IDs present in this row
std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
// - The hyperlink IDs present in this row
std::vector<uint16_t> ATTR_ROW::GetHyperlinks()
{
std::unordered_set<uint16_t> ids;
for (const auto& run : _list)
std::vector<uint16_t> ids;
for (const auto& run : *this)
{
if (run.GetAttributes().IsHyperlink())
if (run.IsHyperlink())
{
ids.emplace(run.GetAttributes().GetHyperlinkId());
ids.emplace_back(run.GetHyperlinkId());
}
}
return ids;
@@ -201,10 +80,8 @@ std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
// - <none>
bool ATTR_ROW::SetAttrToEnd(const UINT iStart, const TextAttribute attr)
{
size_t const length = _cchRowWidth - iStart;
const TextAttributeRun run(length, attr);
return SUCCEEDED(InsertAttrRuns({ &run, 1 }, iStart, _cchRowWidth - 1, _cchRowWidth));
mybase::fill(attr, iStart);
return true;
}
// Method Description:
@@ -217,13 +94,7 @@ bool ATTR_ROW::SetAttrToEnd(const UINT iStart, const TextAttribute attr)
// - <none>
void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept
{
for (auto& run : _list)
{
if (run.GetAttributes() == toBeReplacedAttr)
{
run.SetAttributes(replaceWith);
}
}
mybase::replace(toBeReplacedAttr, replaceWith);
}
// Routine Description:
@@ -242,393 +113,12 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// otherwise STATUS_SUCCESS if we were successful.
[[nodiscard]] HRESULT ATTR_ROW::InsertAttrRuns(const gsl::span<const TextAttributeRun> newAttrs,
const size_t iStart,
const size_t iEnd,
const size_t cBufferWidth)
const size_t /*iEnd*/,
const size_t /*cBufferWidth*/)
try
{
// Definitions:
// Existing Run = The run length encoded color array we're already storing in memory before this was called.
// Insert Run = The run length encoded color array that someone is asking us to inject into our stored memory run.
// New Run = The run length encoded color array that we have to allocate and rebuild to store internally
// which will replace Existing Run at the end of this function.
// Example:
// cBufferWidth = 10.
// Existing Run: R3 -> G5 -> B2
// Insert Run: Y1 -> N1 at iStart = 5 and iEnd = 6
// (rgInsertAttrs is a 2 length array with Y1->N1 in it and cInsertAttrs = 2)
// Final Run: R3 -> G2 -> Y1 -> N1 -> G1 -> B2
// We'll need to know what the last valid column is for some calculations versus iEnd
// because iEnd is specified to us as an inclusive index value.
// Do the -1 math here now so we don't have to have -1s scattered all over this function.
const size_t iLastBufferCol = cBufferWidth - 1;
// If the insertion size is 1, do some pre-processing to
// see if we can get this done quickly.
if (newAttrs.size() == 1)
{
// Get the new color attribute we're trying to apply
const TextAttribute NewAttr = til::at(newAttrs, 0).GetAttributes();
// If the existing run was only 1 element...
// ...and the new color is the same as the old, we don't have to do anything and can exit quick.
if (_list.size() == 1 && _list.at(0).GetAttributes() == NewAttr)
{
return S_OK;
}
// .. otherwise if we internally have a list of 2 or more and we're about to insert a single color
// it's possible that we just walk left-to-right through the row and find a quick exit.
else if (iStart >= 0 && iStart == iEnd)
{
// First we try to find the run where the insertion happens, using lowerBound and upperBound to track
// where we are currently at.
const auto begin = _list.begin();
size_t lowerBound = 0;
size_t upperBound = 0;
for (size_t i = 0; i < _list.size(); i++)
{
const auto curr = begin + i;
upperBound += curr->GetLength();
if (iStart >= lowerBound && iStart < upperBound)
{
// The run that we try to insert into has the same color as the new one.
// e.g.
// AAAAABBBBBBBCCC
// ^
// AAAAABBBBBBBCCC
//
// 'B' is the new color and '^' represents where iStart is. We don't have to
// do anything.
if (curr->GetAttributes() == NewAttr)
{
return S_OK;
}
// If the current run has length of exactly one, we can simply change the attribute
// of the current run.
// e.g.
// AAAAABCCCCCCCCC
// ^
// AAAAADCCCCCCCCC
//
// Here 'D' is the new color.
if (curr->GetLength() == 1)
{
curr->SetAttributes(NewAttr);
return S_OK;
}
// If the insertion happens at current run's lower boundary...
if (iStart == lowerBound && i > 0)
{
const auto prev = std::prev(curr, 1);
// ... and the previous run has the same color as the new one, we can
// just adjust the counts in the existing two elements in our internal list.
// e.g.
// AAAAABBBBBBBCCC
// ^
// AAAAAABBBBBBCCC
//
// Here 'A' is the new color.
if (NewAttr == prev->GetAttributes())
{
prev->IncrementLength();
curr->DecrementLength();
// If we just reduced the right half to zero, just erase it out of the list.
if (curr->GetLength() == 0)
{
_list.erase(curr);
}
return S_OK;
}
}
// If the insertion happens at current run's upper boundary...
if (iStart == upperBound - 1 && i + 1 < _list.size())
{
// ...then let's try our luck with the next run if possible. This is basically the opposite
// of what we did with the previous run.
// e.g.
// AAAAAABBBBBBCCC
// ^
// AAAAABBBBBBBCCC
//
// Here 'B' is the new color.
const auto next = std::next(curr, 1);
if (NewAttr == next->GetAttributes())
{
curr->DecrementLength();
next->IncrementLength();
if (curr->GetLength() == 0)
{
_list.erase(curr);
}
return S_OK;
}
}
}
// Advance one run in the _list.
lowerBound = upperBound;
// The lowerBound is larger than iStart, which means we fail to find an early exit at the run
// where the insertion happens. We can just break out.
if (lowerBound > iStart)
{
break;
}
}
}
}
// If we're about to cover the entire existing run with a new one, we can also make an optimization.
if (iStart == 0 && iEnd == iLastBufferCol)
{
// Just dump what we're given over what we have and call it a day.
_list.assign(newAttrs.begin(), newAttrs.end());
return S_OK;
}
// In the worst case scenario, we will need a new run that is the length of
// The existing run in memory + The new run in memory + 1.
// This worst case occurs when we inject a new item in the middle of an existing run like so
// Existing R3->B5->G2, Insertion Y2 starting at 5 (in the middle of the B5)
// becomes R3->B2->Y2->B1->G2.
// The original run was 3 long. The insertion run was 1 long. We need 1 more for the
// fact that an existing piece of the run was split in half (to hold the latter half).
const size_t cNewRun = _list.size() + newAttrs.size() + 1;
std::vector<TextAttributeRun> newRun;
newRun.reserve(cNewRun);
// We will start analyzing from the beginning of our existing run.
// Use some pointers to keep track of where we are in walking through our runs.
// Get the existing run that we'll be updating/manipulating.
const auto existingRun = _list.begin();
auto pExistingRunPos = existingRun;
const auto pExistingRunEnd = _list.end();
auto pInsertRunPos = newAttrs.begin();
size_t cInsertRunRemaining = newAttrs.size();
size_t iExistingRunCoverage = 0;
// Copy the existing run into the new buffer up to the "start index" where the new run will be injected.
// If the new run starts at 0, we have nothing to copy from the beginning.
if (iStart != 0)
{
// While we're less than the desired insertion position...
while (iExistingRunCoverage < iStart)
{
// Add up how much length we can cover by copying an item from the existing run.
iExistingRunCoverage += pExistingRunPos->GetLength();
// Copy it to the new run buffer and advance both pointers.
newRun.push_back(*pExistingRunPos++);
}
// When we get to this point, we've copied full segments from the original existing run
// into our new run buffer. We will have 1 or more full segments of color attributes and
// we MIGHT have to cut the last copied segment's length back depending on where the inserted
// attributes will fall in the final/new run.
// Some examples:
// - Starting with the original string R3 -> G5 -> B2
// - 1. If the insertion is Y5 at start index 3
// We are trying to get a result/final/new run of R3 -> Y5 -> B2.
// We just copied R3 to the new destination buffer and we cang skip down and start inserting the new attrs.
// - 2. If the insertion is Y3 at start index 5
// We are trying to get a result/final/new run of R3 -> G2 -> Y3 -> B2.
// We just copied R3 -> G5 to the new destination buffer with the code above.
// But the insertion is going to cut out some of the length of the G5.
// We need to fix this up below so it says G2 instead to leave room for the Y3 to fit in
// the new/final run.
// Fetch out the length so we can fix it up based on the below conditions.
size_t length = newRun.back().GetLength();
// If we've covered more cells already than the start of the attributes to be inserted...
if (iExistingRunCoverage > iStart)
{
// ..then subtract some of the length of the final cell we copied.
// We want to take remove the difference in distance between the cells we've covered in the new
// run and the insertion point.
// (This turns G5 into G2 from Example 2 just above)
length -= (iExistingRunCoverage - iStart);
}
// Now we're still on that "last cell copied" into the new run.
// If the color of that existing copied cell matches the color of the first segment
// of the run we're about to insert, we can just increment the length to extend the coverage.
if (newRun.back().GetAttributes() == pInsertRunPos->GetAttributes())
{
length += pInsertRunPos->GetLength();
// Since the color matched, we have already "used up" part of the insert run
// and can skip it in our big "memcopy" step below that will copy the bulk of the insert run.
cInsertRunRemaining--;
pInsertRunPos++;
}
// We're done manipulating the length. Store it back.
newRun.back().SetLength(length);
}
// Bulk copy the majority (or all, depending on circumstance) of the insert run into the final run buffer.
std::copy_n(pInsertRunPos, cInsertRunRemaining, std::back_inserter(newRun));
// We're technically done with the insert run now and have 0 remaining, but won't bother updating its pointers
// and counts any further because we won't use them.
// Now we need to move our pointer for the original existing run forward and update our counts
// on how many cells we could have copied from the source before finishing off the new run.
while (iExistingRunCoverage <= iEnd)
{
FAIL_FAST_IF(!(pExistingRunPos != pExistingRunEnd));
iExistingRunCoverage += pExistingRunPos->GetLength();
pExistingRunPos++;
}
// If we still have original existing run cells remaining, copy them into the final new run.
if (pExistingRunPos != pExistingRunEnd || iExistingRunCoverage != (iEnd + 1))
{
// We advanced the existing run pointer and its count to on or past the end of what the insertion run filled in.
// If this ended up being past the end of what the insertion run covers, we have to account for the cells after
// the insertion run but before the next piece of the original existing run.
// The example in this case is if we had...
// Existing Run = R3 -> G5 -> B2 -> X5
// Insert Run = Y2 @ iStart = 7 and iEnd = 8
// ... then at this point in time, our states would look like...
// New Run so far = R3 -> G4 -> Y2
// Existing Run Pointer is at X5
// Existing run coverage count at 3 + 5 + 2 = 10.
// However, in order to get the final desired New Run
// (which is R3 -> G4 -> Y2 -> B1 -> X5)
// we would need to grab a piece of that B2 we already skipped past.
// iExistingRunCoverage = 10. iEnd = 8. iEnd+1 = 9. 10 > 9. So we skipped something.
if (iExistingRunCoverage > (iEnd + 1))
{
// Back up the existing run pointer so we can grab the piece we skipped.
pExistingRunPos--;
// If the color matches what's already in our run, just increment the count value.
// This case is slightly off from the example above. This case is for if the B2 above was actually Y2.
// That Y2 from the existing run is the same color as the Y2 we just filled a few columns left in the final run
// so we can just adjust the final run's column count instead of adding another segment here.
if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
{
size_t length = newRun.back().GetLength();
length += (iExistingRunCoverage - (iEnd + 1));
newRun.back().SetLength(length);
}
else
{
// If the color didn't match, then we just need to copy the piece we skipped and adjust
// its length for the discrepancy in columns not yet covered by the final/new run.
// Move forward to a blank spot in the new run
newRun.emplace_back();
// Copy the existing run's color information to the new run
newRun.back().SetAttributes(pExistingRunPos->GetAttributes());
// Adjust the length of that copied color to cover only the reduced number of columns needed
// now that some have been replaced by the insert run.
newRun.back().SetLength(iExistingRunCoverage - (iEnd + 1));
}
// Now that we're done recovering a piece of the existing run we skipped, move the pointer forward again.
pExistingRunPos++;
}
// OK. In this case, we didn't skip anything. The end of the insert run fell right at a boundary
// in columns that was in the original existing run.
// However, the next piece of the original existing run might happen to have the same color attribute
// as the final piece of what we just copied.
// As an example...
// Existing Run = R3 -> G5 -> B2.
// Insert Run = B5 @ iStart = 3 and iEnd = 7
// New Run so far = R3 -> B5
// New Run desired when done = R3 -> B7
// Existing run pointer is on B2.
// We want to merge the 2 from the B2 into the B5 so we get B7.
else if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
{
// Add the value from the existing run into the current new run position.
size_t length = newRun.back().GetLength();
length += pExistingRunPos->GetLength();
newRun.back().SetLength(length);
// Advance the existing run position since we consumed its value and merged it in.
pExistingRunPos++;
}
// Now bulk copy any segments left in the original existing run
if (pExistingRunPos < pExistingRunEnd)
{
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), std::back_inserter(newRun));
}
}
// OK, phew. We're done. Now we just need to free the existing run and store the new run in its place.
_list.swap(newRun);
mybase::replace(iStart, mybase::npos, newAttrs.begin(), newAttrs.end());
return S_OK;
}
// Routine Description:
// - packs a vector of TextAttribute into a vector of TextAttributeRun
// Arguments:
// - attrs - text attributes to pack
// Return Value:
// - packed text attribute run
std::vector<TextAttributeRun> ATTR_ROW::PackAttrs(const std::vector<TextAttribute>& attrs)
{
std::vector<TextAttributeRun> runs;
if (attrs.empty())
{
return runs;
}
for (auto attr : attrs)
{
if (runs.empty() || runs.back().GetAttributes() != attr)
{
const TextAttributeRun run(1, attr);
runs.push_back(run);
}
else
{
runs.back().SetLength(runs.back().GetLength() + 1);
}
}
return runs;
}
ATTR_ROW::const_iterator ATTR_ROW::begin() const noexcept
{
return AttrRowIterator(this);
}
ATTR_ROW::const_iterator ATTR_ROW::end() const noexcept
{
return AttrRowIterator::CreateEndIterator(this);
}
ATTR_ROW::const_iterator ATTR_ROW::cbegin() const noexcept
{
return AttrRowIterator(this);
}
ATTR_ROW::const_iterator ATTR_ROW::cend() const noexcept
{
return AttrRowIterator::CreateEndIterator(this);
}
bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept
{
return (a._list.size() == b._list.size() &&
a._list.data() == b._list.data() &&
a._cchRowWidth == b._cchRowWidth);
}
CATCH_RETURN()

View File

@@ -20,30 +20,27 @@ Revision History:
#pragma once
#include "TextAttributeRun.hpp"
#include "AttrRowIterator.hpp"
#include "til/rle.h"
class ATTR_ROW final
#include "TextAttributeRun.hpp"
class ATTR_ROW final : public til::rle<TextAttribute, unsigned int>
{
public:
using const_iterator = typename AttrRowIterator;
using mybase = til::rle<TextAttribute, unsigned int>;
ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr);
using const_iterator = mybase::const_iterator;
using const_reverse_iterator = mybase::const_reverse_iterator;
void Reset(const TextAttribute attr);
using mybase::mybase; // use base constructor
TextAttribute GetAttrByColumn(const size_t column) const;
TextAttribute GetAttrByColumn(const size_t column,
size_t* const pApplies) const;
size_t GetNumberOfRuns() const noexcept;
std::vector<uint16_t> GetHyperlinks();
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);
bool SetAttrToEnd(const unsigned int iStart, const TextAttribute attr);
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept;
void Resize(const size_t newWidth);
@@ -53,22 +50,19 @@ public:
const size_t iEnd,
const size_t cBufferWidth);
static std::vector<TextAttributeRun> PackAttrs(const std::vector<TextAttribute>& attrs);
using mybase::begin;
using mybase::cbegin;
using mybase::cend;
using mybase::end;
const_iterator begin() const noexcept;
const_iterator end() const noexcept;
using mybase::operator==;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
friend class AttrRowIterator;
friend class ROW;
private:
std::vector<TextAttributeRun> _list;
size_t _cchRowWidth;
void Reset(const TextAttribute attr);
#ifdef UNIT_TESTING
friend class AttrRowTests;
friend class CommonState;
#endif
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- LineRendition.hpp
Abstract:
- Enumerated type for the VT line rendition attribute. This determines the
width and height scaling with which each line is rendered.
--*/
#pragma once
enum class LineRendition
{
SingleWidth,
DoubleWidth,
DoubleHeightTop,
DoubleHeightBottom
};
constexpr SMALL_RECT ScreenToBufferLine(const SMALL_RECT& line, const LineRendition lineRendition)
{
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.Left >> scale, line.Top, line.Right >> scale, line.Bottom };
}
constexpr SMALL_RECT BufferToScreenLine(const SMALL_RECT& line, const LineRendition lineRendition)
{
// Use shift left to quickly multiply the Left and Right by 2 for double width lines.
const SHORT scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.Left << scale, line.Top, (line.Right << scale) + scale, line.Bottom };
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,25 +22,23 @@ Revision History:
#include "TextAttribute.hpp"
class TextAttributeRun final
class TextAttributeRun final : public std::pair<TextAttribute, unsigned int>
{
public:
TextAttributeRun() noexcept;
TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept;
using mybase = std::pair<TextAttribute, unsigned int>;
size_t GetLength() const noexcept;
void SetLength(const size_t cchLength) noexcept;
void IncrementLength() noexcept;
void DecrementLength() noexcept;
using mybase::mybase;
const TextAttribute& GetAttributes() const noexcept;
void SetAttributes(const TextAttribute textAttribute) noexcept;
TextAttributeRun(const size_t cchLength, const TextAttribute attr) :
mybase(attr, gsl::narrow<unsigned int>(cchLength))
{
}
private:
size_t _cchLength;
TextAttribute _attributes;
size_t GetLength() const noexcept { return mybase::second; }
void SetLength(const size_t cchLength) noexcept { mybase::second = gsl::narrow<unsigned int>(cchLength); }
void IncrementLength() noexcept { mybase::second++; }
void DecrementLength() noexcept { mybase::second--; }
#ifdef UNIT_TESTING
friend class AttrRowTests;
#endif
const TextAttribute& GetAttributes() const noexcept { return mybase::first; }
void SetAttributes(const TextAttribute textAttribute) noexcept { mybase::first = textAttribute; }
};

View File

@@ -6,23 +6,20 @@
<RootNamespace>bufferout</RootNamespace>
<ProjectName>BufferOut</ProjectName>
<TargetName>ConBufferOut</TargetName>
<ConfigurationType>StaticLibrary</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="..\AttrRow.cpp" />
<ClCompile Include="..\AttrRowIterator.cpp" />
<ClCompile Include="..\cursor.cpp" />
<ClCompile Include="..\OutputCell.cpp" />
<ClCompile Include="..\OutputCellIterator.cpp" />
<ClCompile Include="..\OutputCellRect.cpp" />
<ClCompile Include="..\OutputCellView.cpp" />
<ClCompile Include="..\Row.cpp" />
<ClCompile Include="..\RowCellIterator.cpp" />
<ClCompile Include="..\search.cpp" />
<ClCompile Include="..\TextColor.cpp" />
<ClCompile Include="..\TextAttribute.cpp" />
<ClCompile Include="..\TextAttributeRun.cpp" />
<ClCompile Include="..\textBuffer.cpp" />
<ClCompile Include="..\textBufferCellIterator.cpp" />
<ClCompile Include="..\textBufferTextIterator.cpp" />
@@ -36,16 +33,15 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\AttrRow.hpp" />
<ClInclude Include="..\AttrRowIterator.hpp" />
<ClInclude Include="..\cursor.h" />
<ClInclude Include="..\DbcsAttribute.hpp" />
<ClInclude Include="..\ICharRow.hpp" />
<ClInclude Include="..\LineRendition.hpp" />
<ClInclude Include="..\OutputCell.hpp" />
<ClInclude Include="..\OutputCellIterator.hpp" />
<ClInclude Include="..\OutputCellRect.hpp" />
<ClInclude Include="..\OutputCellView.hpp" />
<ClInclude Include="..\Row.hpp" />
<ClInclude Include="..\RowCellIterator.hpp" />
<ClInclude Include="..\search.h" />
<ClInclude Include="..\TextColor.h" />
<ClInclude Include="..\TextAttribute.h" />

View File

@@ -97,7 +97,12 @@ bool Search::FindNext()
// - Takes the found word and selects it in the screen buffer
void Search::Select() const
{
_uiaData.SelectNewRegion(_coordSelStart, _coordSelEnd);
// Convert buffer selection offsets into the equivalent screen coordinates
// required by SelectNewRegion, taking line renditions into account.
const auto& textBuffer = _uiaData.GetTextBuffer();
const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart);
const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd);
_uiaData.SelectNewRegion(selStart, selEnd);
}
// Routine Description:
@@ -141,7 +146,10 @@ COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction)
const COORD textBufferEndPosition = uiaData.GetTextBufferEndPosition();
if (uiaData.IsSelectionActive())
{
auto anchor = uiaData.GetSelectionAnchor();
// Convert the screen position of the selection anchor into an equivalent
// buffer position to start searching, taking line rendition into account.
auto anchor = textBuffer.ScreenToBufferPosition(uiaData.GetSelectionAnchor());
if (direction == Direction::Forward)
{
textBuffer.GetSize().IncrementInBoundsCircular(anchor);

View File

@@ -30,17 +30,14 @@ PRECOMPILED_INCLUDE = ..\precomp.h
SOURCES= \
..\AttrRow.cpp \
..\AttrRowIterator.cpp \
..\cursor.cpp \
..\OutputCell.cpp \
..\OutputCellIterator.cpp \
..\OutputCellRect.cpp \
..\OutputCellView.cpp \
..\Row.cpp \
..\RowCellIterator.cpp \
..\TextColor.cpp \
..\TextAttribute.cpp \
..\TextAttributeRun.cpp \
..\textBuffer.cpp \
..\textBufferCellIterator.cpp \
..\textBufferTextIterator.cpp \

View File

@@ -42,6 +42,7 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
_currentPatternId{ 0 }
{
// initialize ROWs
_storage.reserve(static_cast<size_t>(screenBufferSize.Y));
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
{
_storage.emplace_back(static_cast<SHORT>(i), screenBufferSize.X, _currentAttributes, this);
@@ -253,7 +254,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// Erase previous character into an N type.
try
{
prevRow.GetCharRow().ClearCell(coordPrevPosition.X);
prevRow.ClearColumn(coordPrevPosition.X);
}
catch (...)
{
@@ -289,14 +290,15 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
// We only need to compensate for leading bytes
if (dbcsAttribute.IsLeading())
{
short const sBufferWidth = GetSize().Width();
const auto cursorPosition = GetCursor().GetPosition();
const auto lineWidth = GetLineWidth(cursorPosition.Y);
// If we're about to lead on the last column in the row, we need to add a padding space
if (GetCursor().GetPosition().X == sBufferWidth - 1)
if (cursorPosition.X == lineWidth - 1)
{
// set that we're wrapping for double byte reasons
CharRow& charRow = GetRowByOffset(GetCursor().GetPosition().Y).GetCharRow();
charRow.SetDoubleBytePadded(true);
auto& row = GetRowByOffset(cursorPosition.Y);
row.SetDoubleBytePadded(true);
// then move the cursor forward and onto the next row
fSuccess = IncrementCursor();
@@ -479,7 +481,7 @@ void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet)
const UINT uiCurrentRowOffset = GetCursor().GetPosition().Y;
// Set the wrap status as appropriate
GetRowByOffset(uiCurrentRowOffset).GetCharRow().SetWrapForced(fSet);
GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet);
}
//Routine Description:
@@ -495,7 +497,7 @@ bool TextBuffer::IncrementCursor()
// Cursor position is stored as logical array indices (starts at 0) for the window
// Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
// So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
const short iFinalColumnIndex = GetSize().RightInclusive();
const short iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().Y) - 1;
// Move the cursor one position to the right
GetCursor().IncrementXPosition(1);
@@ -634,7 +636,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
// Return Value:
// - Coordinate position in screen coordinates of the character just before the cursor.
// - NOTE: Will return 0,0 if already in the top left corner
COORD TextBuffer::_GetPreviousFromCursor() const noexcept
COORD TextBuffer::_GetPreviousFromCursor() const
{
COORD coordPosition = GetCursor().GetPosition();
@@ -648,11 +650,11 @@ COORD TextBuffer::_GetPreviousFromCursor() const noexcept
// Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
if (coordPosition.Y > 0)
{
// move the cursor to the right edge
coordPosition.X = GetSize().RightInclusive();
// and up one line
// move the cursor up one line
coordPosition.Y--;
// and to the right edge
coordPosition.X = GetLineWidth(coordPosition.Y) - 1;
}
}
@@ -800,6 +802,78 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no
_currentAttributes = currentAttributes;
}
void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
{
const auto cursorPosition = GetCursor().GetPosition();
const auto rowIndex = cursorPosition.Y;
auto& row = GetRowByOffset(rowIndex);
if (row.GetLineRendition() != lineRendition)
{
row.SetLineRendition(lineRendition);
// If the line rendition has changed, the row can no longer be wrapped.
row.SetWrapForced(false);
// And if it's no longer single width, the right half of the row should be erased.
if (lineRendition != LineRendition::SingleWidth)
{
const auto fillChar = L' ';
auto fillAttrs = GetCurrentAttributes();
fillAttrs.SetStandardErase();
const size_t fillOffset = GetLineWidth(rowIndex);
const size_t fillLength = GetSize().Width() - fillOffset;
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
row.WriteCells(fillData, fillOffset, false);
// We also need to make sure the cursor is clamped within the new width.
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
}
_NotifyPaint(Viewport::FromDimensions({ 0, rowIndex }, { GetSize().Width(), 1 }));
}
}
void TextBuffer::ResetLineRenditionRange(const size_t startRow, const size_t endRow)
{
for (auto row = startRow; row < endRow; row++)
{
GetRowByOffset(row).SetLineRendition(LineRendition::SingleWidth);
}
}
LineRendition TextBuffer::GetLineRendition(const size_t row) const
{
return GetRowByOffset(row).GetLineRendition();
}
bool TextBuffer::IsDoubleWidthLine(const size_t row) const
{
return GetLineRendition(row) != LineRendition::SingleWidth;
}
SHORT TextBuffer::GetLineWidth(const size_t row) const
{
// Use shift right to quickly divide the width by 2 for double width lines.
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
return GetSize().Width() >> scale;
}
COORD TextBuffer::ClampPositionWithinLine(const COORD position) const
{
const SHORT rightmostColumn = GetLineWidth(position.Y) - 1;
return { std::min(position.X, rightmostColumn), position.Y };
}
COORD TextBuffer::ScreenToBufferPosition(const COORD position) const
{
// Use shift right to quickly divide the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X >> scale, position.Y };
}
COORD TextBuffer::BufferToScreenPosition(const COORD position) const
{
// Use shift left to quickly multiply the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X << scale, position.Y };
}
// Routine Description:
// - Resets the text contents of this buffer with the default character
// and the default current color attributes
@@ -809,8 +883,7 @@ void TextBuffer::Reset()
for (auto& row : _storage)
{
row.GetCharRow().Reset();
row.GetAttrRow().Reset(attr);
row.Reset(attr);
}
}
@@ -837,11 +910,10 @@ void TextBuffer::Reset()
const SHORT TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y;
// rotate rows until the top row is at index 0
const ROW& newTopRow = _storage.at(TopRowIndex);
while (&newTopRow != &_storage.front())
for (int i = 0; i < TopRowIndex; i++)
{
_storage.push_back(std::move(_storage.front()));
_storage.pop_front();
_storage.emplace_back(std::move(_storage.front()));
_storage.erase(_storage.begin());
}
_SetFirstRowIndex(0);
@@ -1230,9 +1302,15 @@ void TextBuffer::_PruneHyperlinks()
// If the buffer does not contain the same reference, we can remove that hyperlink from our map
// This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around
// Get all the hyperlink references in the row we're erasing
auto firstRowRefs = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
if (!firstRowRefs.empty())
const auto hyperlinks = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
if (!hyperlinks.empty())
{
// Move to unordered set so we can use hashed lookup of IDs instead of linear search.
// Only make it an unordered set now because set always heap allocates but vector
// doesn't when the set is empty (saving an allocation in the common case of no links.)
std::unordered_set<uint16_t> firstRowRefs{ hyperlinks.cbegin(), hyperlinks.cend() };
const auto total = TotalRowCount();
// Loop through all the rows in the buffer except the first row -
// we have found all hyperlink references in the first row and put them in refs,
@@ -1254,12 +1332,12 @@ void TextBuffer::_PruneHyperlinks()
break;
}
}
}
// Now delete obsolete references from our map
for (auto hyperlinkReference : firstRowRefs)
{
RemoveHyperlinkFromMap(hyperlinkReference);
// Now delete obsolete references from our map
for (auto hyperlinkReference : firstRowRefs)
{
RemoveHyperlinkFromMap(hyperlinkReference);
}
}
}
@@ -1420,9 +1498,11 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
// - blockSelection: when enabled, only get the rectangular text region,
// as opposed to the text extending to the left/right
// buffer margins
// - bufferCoordinates: when enabled, treat the coordinates as relative to
// the buffer rather than the screen.
// Return Value:
// - the delimiter class for the given char
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const
{
std::vector<SMALL_RECT> textRects;
@@ -1456,6 +1536,13 @@ const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, b
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
}
// If we were passed screen coordinates, convert the given range into
// equivalent buffer offsets, taking line rendition into account.
if (!bufferCoordinates)
{
textRow = ScreenToBufferLine(textRow, GetLineRendition(row));
}
_ExpandTextRow(textRow);
textRects.emplace_back(textRow);
}
@@ -1582,7 +1669,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
}
// We apply formatting to rows if the row was NOT wrapped or formatting of wrapped rows is allowed
const bool shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).GetCharRow().WasWrapForced();
const bool shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).WasWrapForced();
if (trimTrailingWhitespace)
{
@@ -2039,7 +2126,6 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
const short cOldRowsTotal = cOldLastChar.Y + 1;
const short cOldColsTotal = oldBuffer.GetSize().Width();
COORD cNewCursorPos = { 0 };
bool fFoundCursorPos = false;
@@ -2051,9 +2137,19 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// Fetch the row and its "right" which is the last printable character.
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
const CharRow& charRow = row.GetCharRow();
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
const auto newBufferPos = newBuffer.GetCursor().GetPosition();
if (newBufferPos.X == 0)
{
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
newRow.SetLineRendition(row.GetLineRendition());
}
// There is a special case here. If the row has a "wrap"
// flag on it, but the right isn't equal to the width (one
// index past the final valid index in the row) then there
@@ -2063,7 +2159,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// included.)
// As such, adjust the "right" to be the width of the row
// to capture all these spaces
if (charRow.WasWrapForced())
if (row.WasWrapForced())
{
iRight = cOldColsTotal;
@@ -2072,7 +2168,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// piece of padding because of a double byte LEADING
// character, then remove one from the "right" to
// leave this padding out of the copy process.
if (charRow.WasDoubleBytePadded())
if (row.WasDoubleBytePadded())
{
iRight--;
}
@@ -2136,7 +2232,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Only do so if we were not forced to wrap. If we did
// force a word wrap, then the existing line break was
// only because we ran out of space.
if (iRight < cOldColsTotal && !charRow.WasWrapForced())
if (iRight < cOldColsTotal && !row.WasWrapForced())
{
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
@@ -2180,7 +2276,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const COORD coordNewCursor = newCursor.GetPosition();
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
{
if (newBuffer.GetRowByOffset(gsl::narrow_cast<size_t>(coordNewCursor.Y) - 1).GetCharRow().WasWrapForced())
if (newBuffer.GetRowByOffset(gsl::narrow_cast<size_t>(coordNewCursor.Y) - 1).WasWrapForced())
{
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
}
@@ -2213,7 +2309,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
// because the cursor is already on the next line
if (newBuffer.GetRowByOffset(cNewLastChar.Y).GetCharRow().WasWrapForced())
if (newBuffer.GetRowByOffset(cNewLastChar.Y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
@@ -2221,7 +2317,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
// old buffer will be one more than in this buffer, so new need one LESS.
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).GetCharRow().WasWrapForced())
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
@@ -2410,8 +2506,8 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
// all the text into one string and find the patterns in that string
for (auto i = firstRow; i <= lastRow; ++i)
{
auto row = GetRowByOffset(i);
concatAll += row.GetCharRow().GetText();
auto& row = GetRowByOffset(i);
concatAll += row.GetText();
}
// for each pattern we know of, iterate through the string

View File

@@ -49,6 +49,8 @@ filling in the last row, and updating the screen.
#pragma once
#include <vector>
#include "cursor.h"
#include "Row.hpp"
#include "TextAttribute.hpp"
@@ -120,6 +122,16 @@ public:
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
void SetCurrentLineRendition(const LineRendition lineRendition);
void ResetLineRenditionRange(const size_t startRow, const size_t endRow);
LineRendition GetLineRendition(const size_t row) const;
bool IsDoubleWidthLine(const size_t row) const;
SHORT GetLineWidth(const size_t row) const;
COORD ClampPositionWithinLine(const COORD position) const;
COORD ScreenToBufferPosition(const COORD position) const;
COORD BufferToScreenPosition(const COORD position) const;
void Reset();
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
@@ -139,7 +151,7 @@ public:
bool MoveToNextGlyph(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;
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const;
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
@@ -190,7 +202,7 @@ public:
private:
void _UpdateSize();
Microsoft::Console::Types::Viewport _size;
std::deque<ROW> _storage;
std::vector<ROW> _storage;
Cursor _cursor;
SHORT _firstRow; // indexes top row (not necessarily 0)
@@ -210,7 +222,7 @@ private:
void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept;
COORD _GetPreviousFromCursor() const noexcept;
COORD _GetPreviousFromCursor() const;
void _SetWrapOnCurrentRow();
void _AdjustWrapOnCurrentRow(const bool fSet);

View File

@@ -15,8 +15,8 @@ Author(s):
#pragma once
#include "AttrRowIterator.hpp"
#include "CharRow.hpp"
#include "AttrRow.hpp"
#include "OutputCellView.hpp"
#include "../../types/inc/viewport.hpp"
@@ -55,7 +55,7 @@ protected:
OutputCellView _view;
const ROW* _pRow;
AttrRowIterator _attrIter;
ATTR_ROW::const_iterator _attrIter;
const TextBuffer& _buffer;
const Microsoft::Console::Types::Viewport _bounds;
bool _exceeded;

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,7 +12,7 @@
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
IgnorableNamespaces="uap mp rescap">
IgnorableNamespaces="uap mp rescap uap3">
<Identity
Name="WindowsTerminalDev"
@@ -69,27 +69,27 @@
Enabled="false"
DisplayName="ms-resource:AppNameDev" />
</uap5:Extension>
<uap3:Extension Category="windows.appExtensionHost">
<uap3:AppExtensionHost>
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
</uap3:AppExtensionHost>
</uap3:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
<com:Class Id="9f156763-7844-4dc4-b2b1-901f640f5155" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
<com:Class Id="52065414-e077-47ec-a3ac-1cc5455e1b54" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
<desktop5:Verb Id="OpenTerminalDev" Clsid="52065414-e077-47ec-a3ac-1cc5455e1b54" />
</desktop5:ItemType>
<!-- Due to a bug in the OS, this doesn't actually work right -
we'll get a nullptr in our implementation. So this is disabled
temporarily. See MSFT:24623699 for more details.
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
-->
<desktop5:Verb Id="OpenTerminalDev" Clsid="52065414-e077-47ec-a3ac-1cc5455e1b54" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>

View File

@@ -13,7 +13,7 @@
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
IgnorableNamespaces="uap mp rescap uap3">
<Identity
Name="Microsoft.WindowsTerminalPreview"
@@ -64,6 +64,11 @@
<desktop:ExecutionAlias Alias="wt.exe" />
</uap3:AppExecutionAlias>
</uap3:Extension>
<uap3:Extension Category="windows.appExtensionHost">
<uap3:AppExtensionHost>
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
</uap3:AppExtensionHost>
</uap3:Extension>
<uap5:Extension Category="windows.startupTask">
<uap5:StartupTask
TaskId="StartTerminalOnLoginTask"
@@ -73,24 +78,18 @@
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
<com:Class Id="9f156763-7844-4dc4-b2b1-901f640f5155" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
<com:Class Id="02db545a-3e20-46de-83a5-1329b1e88b6b" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
<desktop5:Verb Id="OpenTerminalPre" Clsid="02db545a-3e20-46de-83a5-1329b1e88b6b" />
</desktop5:ItemType>
<!-- Due to a bug in the OS, this doesn't actually work right -
we'll get a nullptr in our implementation. So this is disabled
temporarily. See MSFT:24623699 for more details.
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
<desktop5:Verb Id="OpenTerminalPre" Clsid="02db545a-3e20-46de-83a5-1329b1e88b6b" />
</desktop5:ItemType>
-->
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>

View File

@@ -13,7 +13,7 @@
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
IgnorableNamespaces="uap mp rescap uap3">
<Identity
Name="Microsoft.WindowsTerminal"
@@ -64,6 +64,11 @@
<desktop:ExecutionAlias Alias="wt.exe" />
</uap3:AppExecutionAlias>
</uap3:Extension>
<uap3:Extension Category="windows.appExtensionHost">
<uap3:AppExtensionHost>
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
</uap3:AppExtensionHost>
</uap3:Extension>
<uap5:Extension Category="windows.startupTask">
<uap5:StartupTask
TaskId="StartTerminalOnLoginTask"
@@ -81,16 +86,11 @@
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
<desktop5:Verb Id="OpenTerminalHere" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
<!-- Due to a bug in the OS, this doesn't actually work right -
we'll get a nullptr in our implementation. So this is disabled
temporarily. See MSFT:24623699 for more details.
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
<desktop5:Verb Id="OpenTerminalHere" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
</desktop5:ItemType>
-->
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Windows::Foundation::Collections;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
@@ -37,10 +37,13 @@ namespace SettingsModelLocalTests
TEST_METHOD(ManyCommandsSameAction);
TEST_METHOD(LayerCommand);
TEST_METHOD(TestSplitPaneArgs);
TEST_METHOD(TestSplitPaneBadSize);
TEST_METHOD(TestResourceKeyName);
TEST_METHOD(TestAutogeneratedName);
TEST_METHOD(TestLayerOnAutogeneratedName);
TEST_METHOD(TestGenerateCommandline);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
@@ -147,7 +150,8 @@ namespace SettingsModelLocalTests
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
{ "name": "command4", "command": { "action": "splitPane" } },
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } }
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
{ "name": "command6", "command": { "action": "splitPane", "size": 0.25 } },
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
@@ -156,7 +160,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(4u, commands.Size());
VERIFY_ARE_EQUAL(5u, commands.Size());
{
auto command = commands.Lookup(L"command1");
@@ -167,6 +171,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command2");
@@ -177,6 +182,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command4");
@@ -187,6 +193,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command5");
@@ -197,8 +204,51 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
}
{
auto command = commands.Lookup(L"command6");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
}
}
void CommandTests::TestSplitPaneBadSize()
{
const std::string commands0String{ R"([
{ "name": "command1", "command": { "action": "splitPane", "size": 0.25 } },
{ "name": "command2", "command": { "action": "splitPane", "size": 1.0 } },
{ "name": "command3", "command": { "action": "splitPane", "size": 0 } },
{ "name": "command4", "command": { "action": "splitPane", "size": 50 } },
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(3u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
{
auto command = commands.Lookup(L"command1");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
}
}
void CommandTests::TestResourceKeyName()
{
// This test checks looking up a name from a resource key.
@@ -227,14 +277,6 @@ namespace SettingsModelLocalTests
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.
@@ -321,4 +363,145 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
}
}
void CommandTests::TestGenerateCommandline()
{
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
const std::string commands0String{ R"([
{
"name":"action0",
"command": { "action": "newWindow" }
},
{
"name":"action1",
"command": { "action": "newTab", "profile": "foo" }
},
{
"name":"action2",
"command": { "action": "newWindow", "profile": "foo" }
},
{
"name":"action3",
"command": { "action": "newWindow", "commandline": "bar.exe" }
},
{
"name":"action4",
"command": { "action": "newWindow", "commandline": "pop.exe ya ha ha" }
},
{
"name":"action5",
"command": { "action": "newWindow", "commandline": "pop.exe \"ya ha ha\"" }
},
{
"name":"action6",
"command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" }
},
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(7u, commands.Size());
{
auto command = commands.Lookup(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
VERIFY_ARE_EQUAL(L"", cmdline);
}
{
auto command = commands.Lookup(L"action1");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline);
}
{
auto command = commands.Lookup(L"action2");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline);
}
{
auto command = commands.Lookup(L"action3");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
VERIFY_ARE_EQUAL(L"-- \"bar.exe\"", cmdline);
}
{
auto command = commands.Lookup(L"action4");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
Log::Comment(NoThrowString().Format(
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"-- \"pop.exe ya ha ha\"", terminalArgs.ToCommandline());
}
{
auto command = commands.Lookup(L"action5");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
Log::Comment(NoThrowString().Format(
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"-- \"pop.exe \"ya ha ha\"\"", terminalArgs.ToCommandline());
}
{
auto command = commands.Lookup(L"action6");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
Log::Comment(NoThrowString().Format(
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline());
}
}
}

View File

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

View File

@@ -11,7 +11,7 @@
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::Control;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;

View File

@@ -15,7 +15,7 @@ using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::Control;
namespace SettingsModelLocalTests
{
@@ -84,7 +84,7 @@ namespace SettingsModelLocalTests
"initialPosition": ",",
"launchMode": "default",
"alwaysOnTop": false,
"inputServiceWarning": true,
"copyOnSelect": false,
"copyFormatting": "all",
"wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502",
@@ -226,6 +226,7 @@ namespace SettingsModelLocalTests
const std::string settingsString{ R"({
"$schema": "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
"profiles": {
"defaults": {

View File

@@ -40,6 +40,7 @@
<ClCompile Include="CommandTests.cpp" />
<ClCompile Include="DeserializationTests.cpp" />
<ClCompile Include="SerializationTests.cpp" />
<ClCompile Include="TerminalSettingsTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@@ -57,7 +58,7 @@
<!-- 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\TerminalControl\dll\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
</ItemGroup>

View File

@@ -0,0 +1,543 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../TerminalSettingsModel/TerminalSettings.h"
#include "TestUtils.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Control;
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 TerminalSettingsTests
{
// 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(TerminalSettingsTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(TryCreateWinRTType);
TEST_METHOD(TestTerminalArgsForBinding);
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
TEST_METHOD(TestLayerProfileOnColorScheme);
TEST_CLASS_SETUP(ClassSetup)
{
return true;
}
};
void TerminalSettingsTests::TryCreateWinRTType()
{
TerminalSettings settings;
VERIFY_IS_NOT_NULL(settings);
auto oldFontSize = settings.FontSize();
settings.FontSize(oldFontSize + 5);
auto newFontSize = settings.FontSize();
VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize);
}
void TerminalSettingsTests::TestTerminalArgsForBinding()
{
const std::string settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe"
},
{
"name": "profile1",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 2,
"commandline": "pwsh.exe"
},
{
"name": "profile2",
"historySize": 3,
"commandline": "wsl.exe"
}
],
"keybindings": [
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } },
{ "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile1" } },
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile2" } },
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal", "commandline": "foo.exe" } },
{ "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "horizontal", "profile": "profile1", "commandline": "foo.exe" } },
{ "keys": ["ctrl+g"], "command": { "action": "newTab" } },
{ "keys": ["ctrl+h"], "command": { "action": "newTab", "startingDirectory": "c:\\foo" } },
{ "keys": ["ctrl+i"], "command": { "action": "newTab", "profile": "profile2", "startingDirectory": "c:\\foo" } },
{ "keys": ["ctrl+j"], "command": { "action": "newTab", "tabTitle": "bar" } },
{ "keys": ["ctrl+k"], "command": { "action": "newTab", "profile": "profile2", "tabTitle": "bar" } },
{ "keys": ["ctrl+l"], "command": { "action": "newTab", "profile": "profile1", "tabTitle": "bar", "startingDirectory": "c:\\foo", "commandline":"foo.exe" } }
]
})" };
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") };
CascadiaSettings settings{ til::u8u16(settingsJson) };
auto keymap = settings.GlobalSettings().KeyMap();
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid();
VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid);
VERIFY_ARE_EQUAL(12u, keymap.Size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
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(SplitState::Vertical, realArgs.SplitStyle());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
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(SplitState::Vertical, realArgs.SplitStyle());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
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(SplitState::Vertical, realArgs.SplitStyle());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
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(SplitState::Vertical, realArgs.SplitStyle());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(profile2Guid, guid);
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
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(SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
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(SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
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);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
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);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
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);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(profile2Guid, guid);
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('J') };
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);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid0, guid);
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('K') };
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);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(profile2Guid, guid);
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('L') };
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);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
VERIFY_ARE_EQUAL(guid1, guid);
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
}
}
void TerminalSettingsTests::MakeSettingsForProfileThatDoesntExist()
{
// Test that making settings throws when the GUID doesn't exist
const std::string settingsString{ 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 settings{ til::u8u16(settingsString) };
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}");
try
{
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid1, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(1, terminalSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
}
try
{
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid2, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(2, terminalSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
}
VERIFY_THROWS(auto terminalSettings = TerminalSettings::CreateWithProfileByID(settings, guid3, nullptr), wil::ResultException, L"This call to constructor should fail");
try
{
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to CreateWithNewTerminalArgs should succeed");
}
}
void TerminalSettingsTests::MakeSettingsForDefaultProfileThatDoesntExist()
{
// Test that MakeSettings _doesnt_ throw when we load settings with a
// defaultProfile that's not in the list, we validate the settings, and
// then call MakeSettings(nullopt). The validation should ensure that
// the default profile is something reasonable
const std::string settingsString{ R"(
{
"defaultProfile": "{6239a42c-3333-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 settings{ til::u8u16(settingsString) };
VERIFY_ARE_EQUAL(2u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size());
VERIFY_ARE_EQUAL(settings.GlobalSettings().DefaultProfile(), settings.ActiveProfiles().GetAt(0).Guid());
try
{
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to CreateWithNewTerminalArgs should succeed");
}
}
void TerminalSettingsTests::TestLayerProfileOnColorScheme()
{
Log::Comment(NoThrowString().Format(
L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly."));
const std::string settings0String{ R"(
{
"defaultProfile": "profile5",
"profiles": [
{
"name" : "profile0",
"colorScheme": "schemeWithCursorColor"
},
{
"name" : "profile1",
"colorScheme": "schemeWithoutCursorColor"
},
{
"name" : "profile2",
"colorScheme": "schemeWithCursorColor",
"cursorColor": "#234567"
},
{
"name" : "profile3",
"colorScheme": "schemeWithoutCursorColor",
"cursorColor": "#345678"
},
{
"name" : "profile4",
"cursorColor": "#456789"
},
{
"name" : "profile5"
}
],
"schemes": [
{
"name": "schemeWithCursorColor",
"cursorColor": "#123456"
},
{
"name": "schemeWithoutCursorColor"
}
]
})" };
CascadiaSettings settings{ til::u8u16(settings0String) };
VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size());
VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size());
auto createTerminalSettings = [&](const auto& profile, const auto& schemes) {
auto terminalSettings{ winrt::make_self<implementation::TerminalSettings>() };
terminalSettings->_ApplyProfileSettings(profile, schemes);
return terminalSettings;
};
auto terminalSettings0 = createTerminalSettings(settings.ActiveProfiles().GetAt(0), settings.GlobalSettings().ColorSchemes());
auto terminalSettings1 = createTerminalSettings(settings.ActiveProfiles().GetAt(1), settings.GlobalSettings().ColorSchemes());
auto terminalSettings2 = createTerminalSettings(settings.ActiveProfiles().GetAt(2), settings.GlobalSettings().ColorSchemes());
auto terminalSettings3 = createTerminalSettings(settings.ActiveProfiles().GetAt(3), settings.GlobalSettings().ColorSchemes());
auto terminalSettings4 = createTerminalSettings(settings.ActiveProfiles().GetAt(4), settings.GlobalSettings().ColorSchemes());
auto terminalSettings5 = createTerminalSettings(settings.ActiveProfiles().GetAt(5), settings.GlobalSettings().ColorSchemes());
VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default
VERIFY_ARE_EQUAL(ARGB(0, 0x23, 0x45, 0x67), terminalSettings2->CursorColor()); // from profile (trumps color scheme)
VERIFY_ARE_EQUAL(ARGB(0, 0x34, 0x56, 0x78), terminalSettings3->CursorColor()); // from profile (not set in color scheme)
VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme)
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default
}
}

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