Compare commits

...

106 Commits

Author SHA1 Message Date
Dustin L. Howett
6e1ce8de26 Migrate spelling-0.0.21 changes from main 2020-06-26 14:39:45 -05:00
Dustin L. Howett
5e13c37217 Migrate spelling-0.0.19 changes from main 2020-06-26 14:39:45 -05:00
Mike Griese
6328b1adda Merge branch 'dev/migrie/f/2046-Command-Palette-with-autogen-action-names' into dev/migrie/f/execute-commandlines
# Conflicts:
#	src/cascadia/TerminalApp/ActionAndArgs.cpp
#	src/cascadia/TerminalApp/ActionArgs.cpp
#	src/cascadia/TerminalApp/ActionArgs.h
#	src/cascadia/TerminalApp/ActionArgs.idl
#	src/cascadia/TerminalApp/CommandPalette.cpp
#	src/cascadia/TerminalApp/ShortcutActionDispatch.cpp
#	src/cascadia/TerminalApp/ShortcutActionDispatch.h
#	src/cascadia/TerminalApp/ShortcutActionDispatch.idl
#	src/cascadia/TerminalApp/TerminalPage.cpp
#	src/cascadia/TerminalControl/TermControl.idl
2020-06-26 14:39:45 -05:00
Mike Griese
177cf1caae use a border instead of a grid 2020-06-26 08:37:22 -05:00
Mike Griese
75473c50fb Use the visibility changed event, not the ToggleVisibility function.
Also misc spellchecks, nits.
2020-06-26 07:51:48 -05:00
Mike Griese
76cb9654e1 hey, why not use keybindings directly from the command palette? 2020-06-25 12:33:05 -05:00
Mike Griese
e967801cdf actually, just do all of #6645 2020-06-25 11:54:51 -05:00
Mike Griese
50cfd84dd2 add a little message when no matches are found 2020-06-25 11:34:02 -05:00
Mike Griese
33860f7c1a good bot 2020-06-25 11:17:28 -05:00
Mike Griese
860a3c7b23 add support for clicking on individual items in the command palette 2020-06-25 11:16:29 -05:00
Mike Griese
eae88a9fdb light dismiss on click, better sizing 2020-06-25 10:33:48 -05:00
Mike Griese
137aea367a unordered_maps, mad genius sorting, and a good idea about ESC 2020-06-25 09:37:46 -05:00
Mike Griese
8a93b1dd5d Merge remote-tracking branch 'origin/master' into dev/migrie/f/2046-Command-Palette-with-autogen-action-names
# Conflicts:
#	src/cascadia/TerminalApp/ActionAndArgs.cpp
#	src/cascadia/TerminalApp/ActionArgs.cpp
#	src/cascadia/TerminalApp/AppActionHandlers.cpp
#	src/cascadia/TerminalApp/ShortcutActionDispatch.cpp
#	src/cascadia/TerminalApp/ShortcutActionDispatch.h
#	src/cascadia/TerminalApp/ShortcutActionDispatch.idl
#	src/cascadia/TerminalApp/TerminalPage.cpp
#	src/cascadia/TerminalApp/TerminalPage.h
2020-06-25 09:13:51 -05:00
Mike Griese
a3a9df82b5 Add setTabColor and openTabColorPicker actions (#6567)
## Summary of the Pull Request

Adds a pair of `ShortcutAction`s for setting the tab color.
* `setTabColor`: This changes the color of the current tab to the provided color, or can be used to clear the color.
* `openTabColorPicker`: This keybinding immediately activates the tab color picker for the currently focused tab.

## References

## PR Checklist
* [x] scratches my own itch
* [x] I work here
* [x] Tests added/passed
* [x] https://github.com/MicrosoftDocs/terminal/pull/69

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
* hey look there are tests
* Tested with the following:
```json

        // { "command": "setTabColor", "keys": [ "alt+c" ] },
        { "keys": "ctrl+alt+c", "command": { "action": "setTabColor", "color": "#123456" } },
        { "keys": "alt+shift+c", "command": { "action": "setTabColor", "color": null} },
        { "keys": "alt+c", "command": "openTabColorPicker" },
```
2020-06-25 13:06:21 +00:00
Carlos Zamora
9215b5282d Implement Shift+MultiClick Selection Expansion (#6322)
This pull request implements shift+double/triple click. Proper behavior
(as described in #4557) is to only expand one selection point, not both.

Adding the `bool targetStart` was a bit weird. I decided on this being
the cleanest approach though because I still want `PivotSelection` to be
its own helper function. Otherwise, the concept of "pivoting" gets kinda
messy.

## Validation Steps Performed
Manual testing as described on attached issue.
Tests were added for Shift+Click and pivoting the selection too.

Closes #4557
2020-06-25 00:47:13 +00:00
Dustin L. Howett
cffd4eb8e9 Revert "Skip ... analysis when the ... text is simple (6206)" (#6665)
This reverts commit 94eab6e391.

We'll reintroduce this again after making sure it plays nicely with
recycling and box drawing glyphs.

Fixes #6488
Fixes #6664
2020-06-24 15:24:18 -07:00
Mike Griese
6ca7d0e054 use iterators, because this isn't 1999 2020-06-24 16:59:35 -05:00
Mike Griese
6f1dc5c9fa Make command fuzzy-searching heaps better
Weight consecutive matched chars heigher.
  Sort results with the same weight.
  Co-authored by: @greg904
2020-06-24 16:40:22 -05:00
Rushil Kasetty
4027ba37a6 Add keybinding to rename tab (#6557)
<!-- 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 keybinding for renaming a tab
<!-- 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] Fulfills format requirements set by #6567 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [X] Tests passed
* [X] 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: #6567 and here (#6557)

This no longer c loses #6256, as the spec changed. 
<!-- 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-06-24 20:07:41 +00:00
Mike Griese
b5be04c3ae only the simplest remaining feedback 2020-06-24 14:33:24 -05:00
Dustin L. Howett
d8810f2730 version: bump to 1.2 on master 2020-06-23 18:12:00 -07:00
Mike Griese
48d4e248b7 misc fixes from PR 2020-06-23 16:54:42 -05:00
greg904
58f5d7c72e Update _TerminalCursorPositionChanged to use ThrottledFunc (#6492)
* Update _TerminalCursorPositionChanged to use ThrottledFunc.
* Rename previous ThrottledFunc to ThrottledArgFunc because now
  ThrottledFunc is for functions that do not take an argument.
* Update ThrottledFunc and ThrottledArgFunc to accept a CoreDispatcher
  on which the function should be called for convenience.
* Don't use coroutines/winrt::fire_and_forget in
  ThrottledFunc/ThrottledArgFunc because they are too slow (see PR).

_AdjustCursorPosition went from 17% of samples to 3% in performance
testing.
2020-06-23 14:05:40 -07:00
Mingjie Zhao
b24dbf7c77 Replace std::map with std::unordered_map (#6640)
Replace std::map with std::unordered_map when the order doesn't matter
and hash functions are provided. Simple optimizations, but I expect the
performance should be strictly better, especially for
CodepointWidthDetector.hpp.
2020-06-23 20:49:07 +00:00
Mike Griese
264618ae88 a ton of code cleanup from review 2020-06-23 13:01:27 -05:00
Mike Griese
f892e52077 align visuals with the SearchBoxControl 2020-06-23 11:50:40 -05:00
pi1024e
ff23be04fb Optimize booleans (#6548)
<!-- 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
Many places in this codebase has an equality comparison to the boolean FALSE. This adds unneeded complexity as C and C++ has a NOT operand for use of these in if statements. This makes the code more readable in those areas.

<!-- 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] 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
* [ ] 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
One boolean being compared to FALSE was only used once, with the boolean name being "b", so it is better off not existing at all.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Unit Testing passed, compiler refactoring
2020-06-22 21:51:34 +00:00
Mike Griese
81eb13542a Spec for unified keybindings and commands, and synthesized action names (#6532)
## Summary of the Pull Request

This is another iteration on the Command Palette spec, from #5674. These were some ideas that were tossed around by @DHowett, @cinnamon-msft and myself, formalized here. I proposed this as an addendum to the original spec, since I think the first made sense atomically, and this only makes sense as a set of changes to the original. I didn't want to go hacking up the original doc to add this set of changes. 

**There are two proposals in this spec - they should be viewed as two atomic units. They can be accepted or rejected independently. I'm suggesting we approve both. They work _together_. I'm realizing now that this is worded confusingly, and it's on me to fix that.**

## PR Checklist
* [x] Another spec in the #2046 / #5400 saga
* [x] I work here
* [x] _is a doc_

> ## Abstract
> 
> This document is intended to serve as an addition to the [Command Palette Spec].
> While that spec is complete in it's own right, subsequent discussion revealed
> additional ways to improve the functionality and usability of the command
> palette. This document builds largely on the topics already introduced in the
> original spec, so readers should first familiarize themselves with that
> document.
> 
> One point of note from the original document was that the original specification
> was entirely too verbose when defining both keybindings and commands for
> actions. Consider, for instance, a user that wants to bind the action "duplicate
> the current pane". In that spec, they need to add both a keybinding and a
> command:
> 
> ```json
> {
>     "keybindings": [
>         { "keys": [ "ctrl+alt+t" ], "command": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" } },
>     ],
>     "commands": [
>         { "name": "Duplicate Pane", "action": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" }, "icon": null },
>     ]
> }
> ```
> 
> These two entries are practically the same, except for two key differentiators:
> * the keybinding has a `keys` property, indicating which key chord activates the
>   action.
> * The command has a `name` property, indicating what name to display for the
>   command in the Command Palette.
> 
> What if the user didn't have to duplicate this action? What if the user could
> just add this action once, in their `keybindings` or `commands`, and have it
> work both as a keybinding AND a command?
>
2020-06-22 15:41:45 -05:00
greg904
073e732301 Double-click a tab to rename it (#6628)
<!-- 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

When the user double clicks on a tab, show the tab rename box
as if they right clicked on the tab and clicked on "Rename".

<!-- 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 #6600
* [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

I added a handler for the `DoubleTapped` event on the tab view item
when we are constructing it for the tab (in `Tab::_MakeTabViewItem`).

The code for that handler was copied the "rename tab menu item" click
handler.

I did not extract the code into a member function because it is very
short (only 2 lines of code) and only used twice so it is not worth
it IMO.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2020-06-22 16:17:25 +00:00
Michael Niksa
e7d3dc5da2 Recycle assorted rendering components to accelerate drawing (#6483)
This saves an awful lot of construction/destruction and memory
allocation, especially with text that changes a lot (see: cacafire).

Three things:
1. Recycling the text layouts. This holds onto the `CustomTextLayout` so
   all the things that don't change related to drawing targets and
   whatnot aren't freed and recreated every frame.
2. Reordering the runs in place. This saves a vector
   allocation/copy/delete every time OrderRuns is called. They can be
   rearranged in place.
3. Only clip once per row. This reduces the clip push/pop to only one
   time per row. Since we're always redrawing an entire row at a time,
   this saves a lot of alloc/free of the clip frame, dramatically
   reduces queued commands, and makes less work on the flush since
   clipping requires staging the drawing and then bringing it back to
   the main surface.
2020-06-22 16:13:09 +00:00
Mike Griese
b90cc38f0f Merge remote-tracking branch 'origin/master' into dev/migrie/f/2046-Command-Palette-with-autogen-action-names 2020-06-22 11:03:32 -05:00
Mike Griese
92b3453119 clean up these tests 2020-06-22 11:03:14 -05:00
Mike Griese
e25da60775 fix the last case of CommandTests::LayerCommand 2020-06-22 10:49:12 -05:00
greg904
26d6a37800 Always use the dark window borders (#6624) 2020-06-22 08:38:07 -07:00
Mike Griese
cee5fc9226 This is a ton of new tests 2020-06-22 10:31:12 -05:00
Michael Niksa
951f389210 Use D2DDeviceContext and friends over D2DRenderTarget (#6527)
I was told that the DeviceContext version supercedes the RenderTarget
one. This moves us to it so we can gain access to a higher level of
control over the various pieces in our pipeline as we continue to evolve
the renderer.

The underlying motivation here is to potentially use a
`ID2D1CommandList` to batch our commands and run them all later outside
the lock. That can only really be done with this more granular level of
control over the pipeline. So this moves to that in a single step that
is easily findable in history should we have problems

I discussed this with @NiklasBorson of the Direct2D/DirectWrite team as
well as with @DHowett before doing it.

## Validation
- [x] Checked docs to make sure that these work on Windows 7 with
  Platform Update
- [x] Manual smoke test real quick
- [ ] Try running on Win7 + Platform Update after change
- [x] Probably do more than just a smoke test manually or otherwise

Closes #6525
2020-06-19 22:14:01 +00:00
Leonard Hecker
15f2535752 Improve bitmap::_calculateArea performance (#6572)
`bitmap::_calculateArea` performance can be improved by leveraging the
optimized `find_first`/`find_next` methods instead of iterating through
the bitmap manually.
2020-06-19 22:09:30 +00:00
Mike Griese
10e91a823d strings for every possible action, and args value 2020-06-19 16:55:41 -05:00
Michael Niksa
b91430b64d Enable hot reload of renderer settings that aren't already hot reload capable (#6551)
## Summary of the Pull Request

## PR Checklist
* [x] Closes #3927
* [x] I work here.
* [x] Tested manually.
* [x] Requires documentation to be updated: (generate doc bug here)
* [x] Am core contributor.

## Detailed Description of the Pull Request / Additional comments
- I found four settings that weren't hot reloadable with the 3927 comment above them:
1. Experimental retro terminal effect
2. Experimental software rendering
3. Experimental full repaint rendering
4. Antialiasing settings for text

I made them all hot reloadable by telling the `TermControl` to propagate them on settings change to the `DxEngine`.
Then I set up the `DxEngine` inside the setters to only set them if they changed. And if they do change, to trigger a full repaint and/or a complete drop and recreate of the entire DX device chain (as would happen if it were lost for another reason like a user-mode graphics failure, disconnected display, etc.)
I made the boolean an atomic because the settings can be coming in off of another thread (the XAML eventing one) and the renderer is picking the status up on its thread at the top of the BeginPaint frame.

## Validation Steps Performed
- [x] Opened it up and toggled all the settings while staring at PowerShell
- [x] Opened it up and toggled all the settings while staring at something intensive like a `cacafire` fire
2020-06-19 21:09:37 +00:00
Mike Griese
d1a72613c7 Use resources for the majority of command names 2020-06-19 15:46:13 -05:00
Mike Griese
3e5eb73597 proof of concept - have the names auto-generated from actions 2020-06-19 14:17:23 -05:00
Carlos Zamora
dc5baab3fe Add schema check to PR template (#6599)
This adds a check for updating the schema, and rewords the documentation checkbox to match the wording of the others.
2020-06-19 12:06:23 -07:00
Mike Griese
cc2fd9b490 fix this build 2020-06-19 11:22:14 -05:00
Mike Griese
45243bbee6 Merge remote-tracking branch 'origin/master' into dev/migrie/f/2046-Command-Palette-v2
# Conflicts:
#	src/cascadia/TerminalApp/ActionAndArgs.cpp
2020-06-19 11:09:25 -05:00
Mike Griese
52eb2e5faa Merge commit '827cc42' into dev/migrie/f/2046-Command-Palette-v2
# Conflicts:
#	doc/specs/#2046 - Command Palette.md
2020-06-19 11:07:48 -05:00
Mike Griese
8b094fdfde Merge commit 'db518c0' into dev/migrie/f/2046-Command-Palette-v2 2020-06-19 10:59:07 -05:00
Leonard Hecker
4f55568a17 Improved ATTR_ROW::ReplaceAttrs performance (#6573)
## Summary of the Pull Request

Improve `ATTR_ROW::ReplaceAttrs` performance by only reserving the necessary capacity instead of resizing the new run.
That way `TextAttributeRun`s are only instantiated once instead of twice.

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

## Detailed Description of the Pull Request / Additional comments

Performance could be further improved by directly moving `TextAttributeRun`s into the new vector, but I considered this out of scope for this PR.

## Validation Steps Performed

CPU usage when running `cacafire` is slightly reduced.
2020-06-19 15:55:17 +00:00
Kayla Cinnamon
acd3ba7bd9 Update PR template with link to docs (#6583) 2020-06-19 08:19:28 -07:00
Kayla Cinnamon
e337faaaf8 Add 1.1 blog post to 2.0 roadmap (#6581) 2020-06-19 08:18:58 -07:00
Devon DeJohn
1fdceb0ea9 doc: copyedit the roadmap a little bit (#6587) 2020-06-18 22:17:48 -07:00
Leonard Hecker
4eaa0b83c7 Use early returns in TermControl::_KeyHandler (#6575)
## Summary of the Pull Request

This PR changes `TermControl::_KeyHandler` to use early returns, which you can think of as "guard clauses".
This has the benefit of a reduced nesting level, easier to understand control flow and opens op the way to more complex conditions.

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

## Validation Steps Performed

Everything still works as expected.
2020-06-18 22:24:12 +00:00
Leonard Hecker
78ca722028 Fixed #6377: TerminalCore::_altGrAliasing is undefined by default (#6571)
## Summary of the Pull Request

Fixes #6377. `TerminalCore` does not initialize `_altGrAliasing`. The impact is minimized in WT because it defaults to `true` in higher layers. It's not initialized when WPF is driving.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #6377
* [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
* [ ] 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
2020-06-18 22:09:45 +00:00
Dustin L. Howett
10bc1a6532 Introduce JsonUtilsNew as documented in #5875 (#6355)
Read the [JsonUtils Spec] for more details.

This pull request introduces the next version of JsonUtils. It is in a
separate file for ease of review and testing.

JsonUtilsNew will be renamed in a subsequent commit that rewrites our
JSON deserializers.

### Implementer's Notes

I went with telescoping exceptions for the key parsing code, because
it's totally possible that you can be five keys deep and encounter a
type error. This lets us encode information about all failures in the
chain instead of just the topmost one.

The original JsonUtilsNew code changed to use `decay` everywhere because
the tests wouldn't compile. We want to treat `GetValue<const guid>` _the
same as_ `GetValue<guid>`, and this lets us do so. `decay` is awesome.

I've been developing this with a shim that redirects `JsonUtils.h` to
`JsonUtilsNew.h`. I am not comfortable deleting the original until we've
moved off of it, and that _will_ be the subject of a followup PR.

## Validation Steps Performed

So many tests.

[JsonUtils Spec]: https://github.com/microsoft/terminal/blob/master/doc/cascadia/Json-Utility-API.md

Refs #2550
2020-06-18 00:27:42 +00:00
Leon Liang
6485a2b440 Spec: Advanced Tab Switcher (#3753)
<!-- 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 is the spec for the Advanced Tab Switcher. This would allow the user to navigate through a vertical list of tabs through a UI, similar to those found in VSCode and Visual Studio.

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References
#1502: Feature Request: Advanced Tab Switcher
#973: Ctrl+Tab toggling between two tabs

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Spec for #1502
* [x] CLA signed.
2020-06-17 17:30:24 +00:00
Dustin L. Howett
ffaba38fd4 Remove the WinTelnetEngine (#6526)
Nobody was using it.

Discussed in #2661.
2020-06-17 16:29:49 +00:00
Mike Griese
7a94b8f725 stash these tests in progress 2020-06-11 16:29:06 -05:00
Mike Griese
e2f5f75471 start adding tests 2020-06-11 12:40:57 -05:00
Mike Griese
7ac7fd1de9 Merge remote-tracking branch 'origin/master' into dev/migrie/f/2046-Command-Palette-v2 2020-06-11 12:15:27 -05:00
Mike Griese
dd684cbca1 implement a weighted ordering for command palette entries 2020-06-10 09:59:55 -05:00
Mike Griese
edc8b557e1 Add much better key chord text to the command palette 2020-06-10 08:53:34 -05:00
Mike Griese
82f968d8d5 If a key is bound to that action, then display the keybinding in the palette 2020-06-09 16:58:51 -05:00
Mike Griese
67c7969879 I couldn't tell you how long this took 2020-06-09 16:30:55 -05:00
Mike Griese
b3d8f0e279 Merge remote-tracking branch 'origin/master' into dev/migrie/f/2046-Command-Palette-v2 2020-06-09 12:33:36 -05:00
Mike Griese
10ace041c3 Trim leading whitespace form the commandline 2020-06-05 15:37:31 -05:00
Mike Griese
4d23121a7e A bunch of cleanup for review 2020-06-05 15:30:32 -05:00
Mike Griese
414007fdd8 This works but it's dirty 2020-06-05 15:05:14 -05:00
Mike Griese
72c0601d00 split-pane ; split-pane at runtime doesn't work, pt2
This does work to split multiple panes at runtime. It definitely crashes
  when the panes get too small, we might be able to adapt Pane::CanSplit to
  be smart about when it's children don't quite have a size yet.
2020-06-05 09:30:57 -05:00
Mike Griese
b3225db167 split-pane ; split-pane at runtime doesn't work, pt1
I thought maybe breaking up the dispatcher calls would just magic fix this for me. It doesn't.
2020-06-05 09:29:04 -05:00
Mike Griese
2517183b63 Add incredible support for commandline mode
easy_button.jpg
2020-06-05 09:27:57 -05:00
Mike Griese
9fbcbcc9d8 Cleanup for review 2020-06-05 08:39:03 -05:00
Mike Griese
73aefc18e8 do it with multiple args 2020-06-05 08:01:39 -05:00
Mike Griese
a02585da35 Whoop, this works for parsing a single command! This code's horribly dirty, but _it works_ 2020-06-04 17:00:24 -05:00
Mike Griese
2cf69338cd Add an ExecuteCommandline ShortcutAction, don't implement quite yet 2020-06-04 14:54:03 -05:00
Mike Griese
d71d8d7b32 Ready for review. 2020-06-04 12:29:22 -05:00
Mike Griese
9d411d405c Make the commands a map, so we can override on "name" 2020-06-04 11:48:38 -05:00
Mike Griese
58be8cd117 Tons of commenting 2020-06-04 11:21:46 -05:00
Mike Griese
487f33ee89 Merge commit '6e6979abe' into dev/migrie/f/2046-Command-Palette-v2
# Conflicts:
#	src/cascadia/TerminalApp/ActionAndArgs.cpp
#	src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp
2020-06-04 10:15:40 -05:00
Mike Griese
829beda501 Merge remote-tracking branch 'origin/master' into dev/migrie/f/2046-Command-Palette-v2
# Conflicts:
#	src/cascadia/TerminalApp/GlobalAppSettings.cpp
#	src/cascadia/TerminalApp/GlobalAppSettings.h
2020-06-04 10:02:40 -05:00
Mike Griese
6e6979abe8 Extract ActionAndArgs::FromJson into it's own class, so it can be re-used later 2020-06-04 09:28:21 -05:00
Mike Griese
1fbe8e415d Turns out, the shadow on the menuflyout isn't my fault, it's on everything that uses MUX 2020-05-29 12:45:39 -05:00
Mike Griese
fa93fdc034 Well, this is neat, and works, but requires 18362 and also casts a shadow on the menuflyout??? 2020-05-29 12:19:04 -05:00
Mike Griese
cf6e1f273a Try to do this with a shadow, but it crashes inexplicably 2020-05-29 11:44:38 -05:00
Mike Griese
99059451d8 Return focus to the active control when closed 2020-05-29 11:29:01 -05:00
Mike Griese
a309191461 Rename private methods, fix wraparound logic 2020-05-29 11:14:56 -05:00
Mike Griese
e1be26b184 Do this in WinRTUtils instead of hackily doing it manually 2020-05-29 09:48:30 -05:00
Mike Griese
bc546dbdb0 Add some stability
Don't die when we encounter an unexpected key
  Reload successfully
2020-05-29 09:42:47 -05:00
Mike Griese
00763fda9e Add duplicate pane to the default commands 2020-05-29 07:47:29 -05:00
Mike Griese
b88be4534b Make sure to scroll the selected item into view 2020-05-29 07:43:12 -05:00
Mike Griese
207666e34d Add a ton of default commands 2020-05-29 07:32:43 -05:00
Mike Griese
62b9a0d1c0 Move the action into it's own sub-object 2020-05-28 16:44:38 -05:00
Mike Griese
02f47f49c3 Make the action names map public, so the ToJson in AKBSerialization can use it 2020-05-28 16:40:04 -05:00
Mike Griese
6905065fcf Merge remote-tracking branch 'origin/master' into dev/migrie/f/2046-Command-Palette-v2
# Conflicts:
#	src/cascadia/TerminalApp/TerminalPage.h
2020-05-28 15:29:23 -05:00
Mike Griese
0416a944a2 Hook up the parsing of ActionAndArgss to the command palette 2020-05-28 15:09:45 -05:00
Mike Griese
3627d8abd5 This should have been in the previous commit 2020-05-28 15:08:57 -05:00
Mike Griese
33a9e32736 Get names from the resources if provided as an object, not a string 2020-05-28 12:56:17 -05:00
Mike Griese
75af4cabbb Hook up all the parsing once again 2020-05-28 12:31:49 -05:00
Mike Griese
43bd483962 Blind port these files from the old branch 2020-05-28 11:26:25 -05:00
Mike Griese
0672812e6f good bot 2020-05-28 10:29:09 -05:00
Mike Griese
f53553f4e4 Merge remote-tracking branch 'origin/master' into dev/migrie/s/5400-CommandPalette-v1 2020-05-28 10:27:58 -05:00
Mike Griese
11130a4895 Add some notes about the advanced tab switcher and how that might interplay with this 2020-05-20 09:15:27 -05:00
Mike Griese
0886e8f12d Merge remote-tracking branch 'origin/master' into dev/migrie/s/5400-CommandPalette-v1 2020-05-20 08:47:50 -05:00
Mike Griese
2e564368c8 Move this section to 'future considerations' 2020-05-07 12:17:00 -05:00
Mike Griese
ead76d7f16 Merge remote-tracking branch 'origin/master' into dev/migrie/s/5400-CommandPalette-v1 2020-05-07 12:11:48 -05:00
Mike Griese
614d1b21d6 last little todos for review 2020-04-30 15:20:25 -05:00
Mike Griese
fe640ff894 move this spec out of drafts/ 2020-04-30 15:03:52 -05:00
Mike Griese
c56eb8fd93 Add a ton of text regarding Commandline Mode vs Action Mode 2020-04-30 15:02:59 -05:00
148 changed files with 7621 additions and 482126 deletions

View File

@@ -9,7 +9,8 @@
* [ ] Closes #xxx
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Requires documentation to be updated
* [ ] 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 -->

View File

@@ -1,25 +0,0 @@
<details>
<summary>
:pencil2: Contributor please read this
</summary>
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
:warning: The command is written for posix shells. You can copy the contents of each `perl` command excluding the outer `'` marks and dropping any `'"`/`"'` quotation mark pairs into a file and then run `perl file.pl` from the root of the repository to run the code. Alternatively, you can manually insert the items...
If the listed items are:
* ... **misspelled**, then please *correct* them instead of using the command.
* ... *names*, please add them to `.github/actions/spell-check/dictionary/names.txt`.
* ... APIs, you can add them to a file in `.github/actions/spell-check/dictionary/`.
* ... just things you're using, please add them to an appropriate file in `.github/actions/spell-check/expect/`.
* ... tokens you only need in one place and shouldn't *generally be used*, you can add an item in an appropriate file in `.github/actions/spell-check/patterns/`.
See the `README.md` in each directory for more information.
:microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [:check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink:
</details>
#### :warning: Reviewers
At present, the action that triggered this message will not show its :x: in this PR unless the branch is within this repository.
Thus, you **should** make sure that this comment has been addressed before encouraging the merge bot to merge this PR.

View File

@@ -1,35 +0,0 @@
ACCEPTFILES
ACCESSDENIED
bitfield
bitfields
CLASSNOTAVAILABLE
EXPCMDFLAGS
EXPCMDSTATE
fullkbd
href
IAsync
IBox
IBind
IClass
IComparable
ICustom
IDirect
IExplorer
IMap
IObject
IStorage
LCID
LSHIFT
NCHITTEST
NCLBUTTONDBLCLK
NCRBUTTONDBLCLK
NOAGGREGATION
NOREDIRECTIONBITMAP
oaidl
ocidl
RETURNCMD
rfind
roundf
RSHIFT
SIZENS
tmp

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
powf
sqrtf
isnan

View File

@@ -1,14 +0,0 @@
ACLs
DACL
DACLs
LKG
mfcribbon
microsoft
microsoftonline
osgvsowi
powerrename
powershell
SACLs
tdbuildteamid
vcruntime
visualstudio

View File

@@ -1,63 +0,0 @@
(?:^|/)dirs$
(?:^|/)go\.mod$
(?:^|/)go\.sum$
(?:^|/)package-lock\.json$
(?:^|/)sources(?:|\.dep)$
SUMS$
\.ai$
\.bmp$
\.cer$
\.class$
\.crl$
\.crt$
\.csr$
\.dll$
\.DS_Store$
\.eot$
\.eps$
\.exe$
\.gif$
\.graffle$
\.gz$
\.icns$
\.ico$
\.jar$
\.jpeg$
\.jpg$
\.key$
\.lib$
\.lock$
\.map$
\.min\..
\.mp3$
\.mp4$
\.otf$
\.pbxproj$
\.pdf$
\.pem$
\.png$
\.psd$
\.runsettings$
\.sig$
\.so$
\.svg$
\.svgz$
\.tar$
\.tgz$
\.ttf$
\.woff
\.xcf$
\.xls
\.xpm$
\.yml$
\.zip$
^consolegit2gitfilters\.json$
^dep/
^oss/
^doc/reference/UTF8-torture-test\.txt$
^src/interactivity/onecore/BgfxEngine\.
^src/renderer/wddmcon/WddmConRenderer\.
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
^\.github/actions/spell-check/
^\.gitignore$

View File

@@ -1,15 +0,0 @@
http
td
www
ecma
rapidtables
WCAG
freedesktop
ycombinator
robertelder
kovidgoyal
leonerd
fixterms
uk
winui
appshellintegration

View File

@@ -1,18 +0,0 @@
https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]*
https://aka\.ms/[-a-zA-Z0-9?&=\/_]*
https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
https://[a-z-]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
microsoft/cascadia-code\@[0-9a-fA-F]{40}
\d+x\d+Logo
Scro\&ll
# selectionInput.cpp
:\\windows\\syste\b
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
hostSm\.ProcessString\(L"[^"]+"
\b([A-Za-z])\1{3,}\b

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

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

48
.github/actions/spelling/advice.md vendored Normal file
View File

@@ -0,0 +1,48 @@
<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details>
<summary>
:pencil2: Contributor please read this
</summary>
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
:warning: The command is written for posix shells. If it doesn't work for you, you can manually _add_ (one word per line) / _remove_ items to `expect.txt` and the `excludes.txt` files.
If the listed items are:
* ... **misspelled**, then please *correct* them instead of using the command.
* ... *names*, please add them to `.github/actions/spelling/allow/names.txt`.
* ... APIs, you can add them to a file in `.github/actions/spelling/allow/`.
* ... just things you're using, please add them to an appropriate file in `.github/actions/spelling/expect/`.
* ... tokens you only need in one place and shouldn't *generally be used*, you can add an item in an appropriate file in `.github/actions/spelling/patterns/`.
See the `README.md` in each directory for more information.
:microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink:
<details><summary>If the flagged items are :exploding_head: false positives</summary>
If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).
Please add a file path to the `excludes.txt` file matching the containing file.
File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).
* well-formed pattern.
If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
try adding it to the `patterns.txt` file.
Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.
</details>
</details>

View File

@@ -1,6 +1,6 @@
# Dictionaries are lists of words to accept unconditionally
# Allow files are lists of words to accept unconditionally
While check spelling will complain about a whitelisted word
While check spelling will complain about an expected word
which is no longer present, you can include things here even if
they are not otherwise present in the repository.
@@ -8,13 +8,14 @@ E.g., you could include a list of system APIs here, or potential
contributors (so that if a future commit includes their name,
it'll be accepted).
### Files
## Files
| File | Description |
| ---- | ----------- |
| [Dictionary](dictionary.txt) | Primary US English dictionary |
| [Allow](allow.txt) | Supplements to the dictionary |
| [Chinese](chinese.txt) | Chinese words |
| [Japanese](japanese.txt) | Japanese words |
| [Microsoft](microsoft.txt) | Microsoft brand items |
| [Fonts](fonts.txt) | Font names |
| [Names](names.txt) | Names of people |
| [Colors](colors.txt) | Names of color |

108
.github/actions/spelling/allow/allow.txt vendored Normal file
View File

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

248
.github/actions/spelling/allow/apis.txt vendored Normal file
View File

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

View File

@@ -0,0 +1,117 @@
alice
aliceblue
antiquewhite
blanchedalmond
blueviolet
burlywood
cadetblue
cornflowerblue
cornsilk
cyan
darkblue
darkcyan
darkgoldenrod
darkgray
darkgreen
darkgrey
darkkhaki
darkmagenta
darkolivegreen
darkorange
darkorchid
darkred
darksalmon
darkseagreen
darkslateblue
darkslategray
darkslategrey
darkturquoise
darkviolet
deeppink
deepskyblue
dimgray
dimgrey
dodgerblue
firebrick
floralwhite
forestgreen
gainsboro
ghostwhite
greenyellow
hotpink
indian
indianred
lavenderblush
lawngreen
lemonchiffon
lightblue
lightcoral
lightcyan
lightgoldenrod
lightgoldenrodyellow
lightgray
lightgreen
lightgrey
lightpink
lightsalmon
lightseagreen
lightskyblue
lightslateblue
lightslategray
lightslategrey
lightsteelblue
lightyellow
limegreen
mediumaquamarine
mediumblue
mediumorchid
mediumpurple
mediumseagreen
mediumslateblue
mediumspringgreen
mediumturquoise
mediumvioletred
midnightblue
mintcream
mistyrose
navajo
navajowhite
navyblue
oldlace
olivedrab
orangered
palegoldenrod
palegreen
paleturquoise
palevioletred
papayawhip
peachpuff
peru
powderblue
rebecca
rebeccapurple
rosybrown
royalblue
saddlebrown
sandybrown
seagreen
sienna
skyblue
slateblue
slategray
slategrey
springgreen
steelblue
violetred
webgray
webgreen
webgrey
webmaroon
webpurple
whitesmoke
xaroon
xray
xreen
xrey
xurple
yellowgreen

View File

@@ -1,8 +1,10 @@
Consolas
emoji
emojis
Extralight
Gabriola
Iosevka
MDL
Monofur
Segoe
wght

11
.github/actions/spelling/allow/math.txt vendored Normal file
View File

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

View File

@@ -0,0 +1,85 @@
ACLs
ADMINS
advapi
altform
altforms
appendwttlogging
appx
appxbundle
appxerror
appxmanifest
ATL
backplating
bitmaps
BOMs
CPLs
cpptools
cppvsdbg
CPRs
cryptbase
DACL
DACLs
defaultlib
diffs
disposables
dotnetfeed
DTDs
DWINRT
enablewttlogging
Intelli
IVisual
libucrt
libucrtd
LKG
LOCKFILE
Lxss
mfcribbon
microsoft
microsoftonline
MSAA
msixbundle
MSVC
MSVCP
muxc
netcore
Onefuzz
osgvsowi
PFILETIME
pgc
pgo
pgosweep
powerrename
powershell
propkey
pscustomobject
QWORD
regedit
robocopy
SACLs
sdkddkver
Shobjidl
Skype
SRW
sxs
Sysinternals
sysnative
systemroot
taskkill
tasklist
tdbuildteamid
ucrt
ucrtd
unvirtualized
VCRT
vcruntime
Virtualization
visualstudio
vscode
VSTHRD
winsdkver
wlk
wslpath
wtl
wtt
wttlog
Xamarin

View File

@@ -1,43 +1,66 @@
Anup
austdi
arkthur
Ballmer
bhoj
Bhojwani
Bluloco
carlos
dhowett
Diviness
dsafa
duhowett
DXP
ekg
eryksun
ethanschoonover
Firefox
Gatta
glsl
Gravell
Grie
Griese
Hernan
Howett
Illhardt
iquilezles
italo
jantari
jerrysh
Kaiyu
kimwalisch
KMehrain
KODELIFE
Kodelife
Kourosh
kowalczyk
leonmsft
Lepilleur
lhecker
lukesampson
Macbook
Manandhar
masserano
mbadolato
Mehrain
menger
mgravell
michaelniksa
michkap
migrie
mikegr
mikemaccana
miloush
miniksa
niksa
nvaccess
nvda
oising
oldnewthing
opengl
osgwiki
pabhojwa
panos
paulcam
pauldotknopf
PGP
@@ -45,11 +68,18 @@ Pham
Rincewind
rprichard
Schoonover
shadertoy
Shomnipotence
simioni
Somuah
sonph
sonpham
stakx
talo
thereses
Walisch
WDX
Wellons
Wirt
Wojciech
zadjii

View File

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

117
.github/actions/spelling/excludes.txt vendored Normal file
View File

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

View File

@@ -1,21 +1,31 @@
abcde
abcdef
ABCDEFG
ABCDEFGH
AAAa
AAAAA
AAAAAAAAAAAAA
AAAAAABBBBBBCCC
AAAAABBBBBBCCC
abcd
abcd
ABCDEFGHIJ
abcdefghijk
ABCDEFGHIJKLMNO
abcdefghijklmnop
ABCDEFGHIJKLMNOPQRST
abcdefghijklmnopqrstuvwxyz
ABCG
ABE
BBBBBBBBBBBBBBDDDD
abf
BBBBB
BBBBBBBB
BBBBBCCC
BBBBCCCCC
BBGGRR
EFG
EFGh
QQQQQQQQQQABCDEFGHIJ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
qrstuvwxyz
qwerty
QWERTYUIOP
qwertyuiopasdfg
YYYYYYYDDDDDDDDDDD
ZAAZZ
@@ -27,3 +37,4 @@ ZYXWVUT
ZZBBZ
ZZZBB
ZZZBZ
ZZZZZ

View File

@@ -0,0 +1,6 @@
WCAG
winui
appshellintegration
mdtauk
gfycat
Guake

View File

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

View File

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

12
.github/actions/spelling/reject.txt vendored Normal file
View File

@@ -0,0 +1,12 @@
^attache$
^attacher$
^attachers$
benefitting
occurences?
^dependan.*
^oer$
Sorce
^[Ss]pae.*
^untill$
^untilling$
^wether.*

View File

@@ -1,20 +0,0 @@
name: Spell checking
on:
push:
schedule:
# * is a special character in YAML so you have to quote this string
- cron: '15 * * * *'
jobs:
build:
name: Spell checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.0.0
with:
fetch-depth: 5
- uses: check-spelling/check-spelling@0.0.16-alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bucket: .github/actions
project: spell-check

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

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

View File

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

View File

@@ -56,6 +56,8 @@
"switchToTab",
"toggleFullscreen",
"find",
"setTabColor",
"openTabColorPicker",
"unbound"
],
"type": "string"
@@ -257,6 +259,22 @@
}
]
},
"SetTabColorAction": {
"description": "Arguments corresponding to a Set Tab Color Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "setTabColor" },
"color": {
"$ref": "#/definitions/Color",
"default": null,
"description": "If provided, will set the tab's color to the given value. If omitted, will reset the tab's color."
}
}
}
]
},
"Keybinding": {
"additionalProperties": false,
"properties": {
@@ -272,6 +290,7 @@
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SplitPaneAction" },
{ "$ref": "#/definitions/OpenSettingsAction" },
{ "$ref": "#/definitions/SetTabColorAction" },
{ "type": "null" }
]
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,227 @@
---
author: Leon Liang @leonMSFT
created on: 2019-11-27
last updated: 2020-06-16
issue id: 1502
---
# Advanced Tab Switcher
## Abstract
Currently the user is able to cycle through tabs on the tab bar. However, this horizontal cycling can be pretty inconvenient when the tab titles are long or when there are too many tabs on the tab bar. It could also get hard to see all your available tabs if the tab titles are long and your screen is small. In addition, there's a common use case to quickly switch between two tabs, e.g. when one tab is used as reference and the other is the actively worked-on tab. If the tabs are not right next to each other on the tab bar, it could be difficult to quickly swap between the two. Having the tabs displayed in Most Recently Used (MRU) order would help with this problem. It could also make the user experience better when there are a handful of tabs that are frequently used, but are nowhere near each other on the tab bar.
Having a tab switcher UI, like the ones in Visual Studio and Visual Studio Code, could help with the tab experience. Presenting the tabs vertically in their own little UI allows the user to see more of the tabs at once, compared to scanning the tab row horizontally and scrolling left/right to find the tab you want. The tab order in those tab switchers are also in MRU order by default.
To try to alleviate some of these user scenarios, we want to create a tab switcher similar to the ones found in VSCode and VS. This spec will cover the design of the switcher, and how a user would interact with the switcher. It would be primarily keyboard driven, and would give a pop-up display of a vertical list of tabs. The tab switcher would also be able to display the tabs in Most Recently Used (MRU) order.
## Inspiration
This was mainly inspired by the tab switcher that's found in Visual Studio Code and Visual Studio.
VS Code's tab switcher appears directly underneath the tab bar.
![Visual Studio Code Tab Switcher](img/VSCodeTabSwitcher.png)
Visual Studio's tab switcher presents itself as a box in the middle of the editor.
![Visual Studio Tab Switcher](img/VSTabSwitcher.png)
In terms of navigating the switcher, both VSCode and Visual Studio behave very similarly. Both open with the press of <kbd>ctrl+tab</kbd> and dismiss on release of <kbd>ctrl</kbd>. They both also allow the user to select the tab with the mouse and with <kbd>enter</kbd>. <kbd>esc</kbd> and a mouse click outside of the switcher both dismiss the window as well.
I'm partial towards looking like VSCode's Tab Switcher - specifically because it seems like both their Command Palette and Tab Switcher use the same UI. You can observe this by first bringing up the command palette, then hitting the keybinding to bring up the tab switcher. You'll notice that they're both using the same centered drop-down from the tab row. In fact, hitting the Tab Switcher keybinding in VSCode while the Command Palette is open simply auto fills the search box with "edit active", signifying that the user wants to select one of the tabs to edit, effectively "swapping" to the tab that's highlighted.
Since Terminal now has a command palette, it would be amazing to reuse that UI and simply fill it with the names of a user's currently open tabs!
## Solution Design
To extend upon the command palette, we simply need to create and maintain two Vector<Commands>, where each command will simply dispatch a `SwitchToTab` `ShortcutAction`. One vector will have the commands in tab row order, and the other will be in MRU order. They'll both have to be maintained along with our existing vector of tabs.
These vectors of commands can then be set as the commands to pull from in the command palette, and as long as the tab titles are available in these commands, the command palette will be able to naturally filter through the tabs as a user types in its search bar. Just like the command palette, a user will be able to navigate through the list of tabs with the arrow keys and pointer interactions. As part of this implementation, I can supplement these actions with "tab switcher specific" navigation keybindings that would only work if the command palette is in tab switcher mode.
The `TabSwitcherControl` will use `TerminalPage`'s `ShortcutActionDispatch` to dispatch a `SwitchToTab` `ShortcutAction`. This will eventually cause `TerminalPage::_OnTabSelectionChanged` to be called. We can update the MRU in this function to be sure that changing tabs from the TabSwitcher, clicking on a tab, or nextTab/prevTab-ing will keep the MRU up-to-date. Adding or closing tabs are handled in `_OpenNewTab` and `_CloseFocusedTab`, which will need to be modified to update the command vectors.
## UI/UX Design
The Tab Switcher will reuse a lot of the XAML code that's used in the command palette. This means it'll show up as a drop-down from the horizontal center of the tab row. It'll appear as a single overlay over the whole Terminal window. There will also be a search box on top of the list of tabs. Here's a rough mockup of how the command palette/tab switcher looks like:
![Mockup Command Palette with Tab Titles](img/CommandPaletteExample.png)
Each entry in the list will show the tab's titles and their assigned number for quick switching, and only one line will be highlighted to signify the tab that is currently selected. The top 9 tabs in the list are numbered for quick switching, and the rest of the tabs will simply have an empty space where a number would be.
The list would look (roughly) like this:
```
1 foo (highlighted)
2 boo
3 Windows
4 /c/Users/booboo
5 Git Moo
6 shoo
7 /c/
8 /d/
9 /e/
/f/
/g/
/h/
```
The highlighted line can move up or down, and if the user moves up while the highlighted line is already at the top of the list, the highlight will wrap around to the bottom of the list. Similarly, it will wrap to the top if the highlight is at the bottom of the list and the user moves down.
If there's more tabs than the UI can display, the list of tabs will scroll up/down as the user keeps iterating up/down. Even if some of the numbered tabs (the first 9 tabs) are not visible, the user can still press any number 1 through 9 to quick switch to that tab.
To give an example of what happens after scrolling past the end, imagine a user is starting from the state in the mock above. The user then iterates down past the end of the visible list four times. The below mock shows the result.
```
5 Git Moo
6 shoo
7 /c/
8 /d
9 /e/
/f/
/g/
/h/
/i/
/j/
/k/
/l/ (highlighted)
```
The tabs designated by numbers 1 through 4 are no longer visible (but still quick-switchable), and the list now starts with "Git Moo", which is associated with number 5.
### Using the Switcher
#### Opening the Tab Switcher
The user can press a keybinding named `tabSwitcher` to bring up the command palette UI with a list of tab titles.
The user can also bring up the command palette first, and type a "tab switcher" prefix like "@" into the search bar to switch into "tab switcher mode".
The user will be able to change it to whatever they like.
There will also be an optional `anchor` arg that may be provided to this keybinding.
#### Keeping it open
We use the term `anchor` to illustrate the idea that the UI stays visible as long as something is "anchoring" it down.
Here's an example of how to set the `anchor` key in the settings:
```
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor": "ctrl" }}
```
This user provided the `anchor` key arg, and set it to <kbd>ctrl</kbd>. So, the user would open the UI with <kbd>ctrl+tab</kbd>, and as long as the user is holding <kbd>ctrl</kbd> down, the UI won't dismiss. The moment the user releases <kbd>ctrl</kbd>, the UI dismisses. The `anchor` key needs to be one of the keys in the `openTabSwitcher` keybinding. If it isn't, we'll display a warning dialog in this case saying that the `anchor` key isn't actually part of the keybinding, and the user might run into some weird behavior.
If `openTabSwitcher` is not given an `anchor` key, the switcher will stay visible even after the release of the keybinding.
#### Switching through Tabs
The user will be able to navigate through the switcher with the following keybindings:
- Switching Down: <kbd>tab</kbd> or <kbd>downArrow</kbd>
- Switching Up: <kbd>shift+tab</kbd> or <kbd>upArrow</kbd>
As the user is cycling through the tab list, the selected tab will be highlighted but the terminal won't actually switch focus to the selected tab. This also applies to pointer interaction. Hovering over an item with a mouse will highlight the item but not switch to the tab.
#### Closing the Switcher and Bringing a Tab into Focus
There are two _dismissal_ keybindings:
1. <kbd>enter</kbd> : brings the currently selected tab into focus and dismisses the UI.
2. <kbd>esc</kbd> : dismisses the UI without changing tab focus.
The following are ways a user can dismiss the UI, _whether or not_ the `Anchor` key is provided to `openTabSwitcher`.
1. The user can press a number associated with a tab to instantly switch to the tab and dismiss the switcher.
2. The user can click on a tab to instantly switch to the tab and dismiss the switcher.
3. The user can click outside of the UI to dismiss the switcher without bringing the selected tab into focus.
4. The user can press any of the dismissal keybindings.
If the `anchor` key is provided, then in addition to the above methods, the UI will dismiss upon the release of the `anchor` key.
Pressing the `openTabSwitcher` keychord again will not close the switcher, it'll do nothing.
### Most Recently Used Order
We'll provide a setting that will allow the list of tabs to be presented in either _in-order_ (how the tabs are ordered on the tab bar), or _Most Recently Used Order_ (MRU). MRU means that the tab that the terminal most recently visited will be on the top of the list, and the tab that the terminal has not visited for the longest time will be on the bottom.
There will be an argument for the `openTabSwitcher` action called `displayOrder`. This can be either `inOrder` or `mruOrder`. Making the setting an argument passed into `openTabSwitcher` would allow the user to have one keybinding to open an MRU Tab Switcher, and different one for the In-Order Tab Switcher. For example:
```
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"mruOrder"}}
{"keys": ["ctrl+shift+p"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"inOrder"}}
```
By default (when the arg isn't specified), `displayOrder` will be "mruOrder".
### Numbered Tabs
Similar to how the user can currently switch to a particular tab with a combination of keys such as <kbd>ctrl+shift+1</kbd>, we want to have the tab switcher provide a number to the first nine tabs (1-9) in the list for quick switching. If there are more than nine tabs in the list, then the rest of the tabs will not have a number assigned.
## Capabilities
### Accessibility
- The tab switcher will be using WinUI, and so it'll be automatically linked to the UIA tree. This allows screen readers to find it, and so narrator will be able to navigate the switcher easily.
- The UI is also fully keyboard-driven, with the option of using a mouse to interact with the UI.
- When the tab switcher pops up, the focus immediately swaps to it.
- For the sake of more contrast with the background, we could use a ThemeShadow to bring the UI closer to the user, making the focus clearer.
### Security
This shouldn't introduce any security issues.
### Reliability
How we're updating the MRU is something to watch out for since it triggers on a lot of tab interactions. However, I don't foresee the update taking long at all, and I can't imagine that users can create and delete tabs fast enough to matter.
### Compatibility
- The existing way of navigating horizontally through the tabs on the tab bar should not break.
- These should also be separate keybindings from the keybindings associated with using the tab switcher.
- When a user reorders their tabs on the tab bar, the MRU order remains unchanged. For example:
- Tab Bar:`[cmd(focused), ps, wsl]` and MRU:`[cmd, ps, wsl]`
- Reordered Tab Bar:`[wsl, cmd(focused), ps]` and MRU:`[cmd, ps, wsl]`
### Performance, Power, and Efficiency
## Potential Issues
We'll need to be careful about how the UI is presented depending on different sizes of the terminal. We also should test how the UI looks as it's open and resizing is happening. Visual Studio's tab switcher is a fixed size, and is always in the middle. Even when the VS window is smaller than the tab switcher size, the tab switcher will show up larger than the VS window itself.
![Small Visual Studio Without Tab Switcher](img/VSMinimumSize.png)
![Small Visual Studio With Tab Switcher](img/VSMinimumSizeWithTabSwitcher.png)
Visual Studio Code only allows the user to shrink the window until it hits a minimum width and height. This minimum width and height gives its tab switcher enough space to show a meaningful amount of information.
![Small Visual Studio Code with Tab Switcher](img/VSCodeMinimumTabSwitcherSize.png)
Terminal can't really replicate Visual Studio's version of the tab switcher in this situation. The TabSwitcher needs to be contained within the Terminal. So, if the TabSwitcher is always centered and has a percentage padding from the borders of the Terminal, it'll shrink as Terminal shrinks. Since the Terminal also has a minimum width, the switcher should always have enough space to be usefully visible.
## Future considerations
### Pane Navigation
There was discussion in [#1502] that brought up the idea of pane navigation, inspired by tmux.
![Tmux Tab and Pane Switching](img/tmuxPaneSwitching.png)
Tmux allows the user to navigate directly to a pane and even give a preview of the pane. This would be extremely useful since it would allow the user to see a tree of their open tabs and panes. Currently there's no way to see what panes are open in each tab, so if you're looking for a particular pane, you'd need to cycle through your tabs to find it. If something like pane profile names (not sure what information to present in the switcher for panes) were presented in the TabSwitcher, the user could see all the panes in one box.
To support pane navigation, the tab switcher can simply have another column to the right of the tab list to show a list of panes inside the selected tab. As the user iterates through the tab list, they can simply hit right to dig deeper into the tab's panes, and hit left to come back to the tab list. Each tab's list of panes will be MRU or in-order, depending on which `displayOrder` arg was provided to the `openTabSwitcher` keybinding.
Pane navigation is a clear next step to build on top of the tab switcher, but this spec will specifically deal with just tab navigation in order to keep the scope tight. The tab switcher implementation just needs to allow for pane navigation to be added in later.
### Tab Preview on Hover
With this feature, having a tab highlighted in the switcher would make the Terminal display that tab as if it switched to it. I believe currently there is no way to set focus to a tab in a "preview" mode. This is important because MRU updates whenever a tab is focused, but we don't want the MRU to update on a preview. Given that this feature is a "nice thing to have", I'll leave it for
after the tab switcher has landed.
## Resources
Feature Request: An advanced tab switcher [#1502]
Ctrl+Tab toggle between last two windows like Alt+Tab [#973]
The Command Palette Thread [#2046]
The Command Palette Spec [#5674]
Feature Request: Search [#605]
<!-- Footnotes -->
[#605]: https://github.com/microsoft/terminal/issues/605
[#973]: https://github.com/microsoft/terminal/issues/973
[#1502]: https://github.com/microsoft/terminal/issues/1502
[#2046]: https://github.com/microsoft/terminal/issues/2046
[#5674]: https://github.com/microsoft/terminal/pull/5674

View File

@@ -1,7 +1,7 @@
---
author: Mike Griese @zadjii-msft
created on: 2019-08-01
last updated: 2020-06-10
last updated: 2020-06-16
issue id: 2046
---
@@ -522,6 +522,13 @@ default. These are largely the actions that are bound by default.
]
```
## Addenda
This spec also has a follow-up spec which introduces further changes upon this
original draft. Please also refer to:
* June 2020: Unified keybindings and commands, and synthesized action names.
## Future considerations
* Commands will provide an easy point for allowing an extension to add its

View File

@@ -0,0 +1,608 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-06-15
last updated: 2020-06-19
issue id: 2046
---
# Command Palette, Addendum 1 - Unified keybindings and commands, and synthesized action names
## Abstract
This document is intended to serve as an addition to the [Command Palette Spec].
While that spec is complete in it's own right, subsequent discussion revealed
additional ways to improve the functionality and usability of the command
palette. This document builds largely on the topics already introduced in the
original spec, so readers should first familiarize themselves with that
document.
One point of note from the original document was that the original specification
was entirely too verbose when defining both keybindings and commands for
actions. Consider, for instance, a user that wants to bind the action "duplicate
the current pane". In that spec, they need to add both a keybinding and a
command:
```json
{
"keybindings": [
{ "keys": [ "ctrl+alt+t" ], "command": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" } },
],
"commands": [
{ "name": "Duplicate Pane", "action": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" }, "icon": null },
]
}
```
These two entries are practically the same, except for two key differentiators:
* the keybinding has a `keys` property, indicating which key chord activates the
action.
* The command has a `name` property, indicating what name to display for the
command in the Command Palette.
What if the user didn't have to duplicate this action? What if the user could
just add this action once, in their `keybindings` or `commands`, and have it
work both as a keybinding AND a command?
## Solution Design
This spec will outline two primary changes to keybindings and commands.
1. Unify keybindings and commands, so both `keybindings` and `commands` can
specify either actions bound to keys, and/or actions bound to entries in the
Command Palette.
2. Propose a mechanism by which actions do not _require_ a `name` to appear in
the Command Palette.
These proposals are two atomic units - either could be approved or rejected
independently of one another. They're presented together here in the same doc
because together, they present a compelling story.
### Proposal 1: Unify Keybindings and Commands
As noted above, keybindings and commands have nearly the exact same syntax, save
for a couple properties. To make things easier for the user, I'm proposing
treating everything in _both_ the `keybindings` _and_ the `commands` arrays as
**BOTH** a keybinding and a command.
Furthermore, as a change from the previous spec, we'll be using `bindings` from
here on as the unified `keybindings` and `commands` lists. This is considering
that we'll currently be using `bindings` for both commands and keybindings, but
we'll potentially also have mouse & touch bindings in this array in the future.
We'll "deprecate" the existing `keybindings` property, and begin to exclusively
use `bindings` as the new property name. For compatibility reasons, we'll
continue to parse `keybindings` in the same way we parse `bindings`. We'll
simply layer `bindings` on top of the legacy `keybindings`.
* Anything entry that has a `keys` value will be added to the keybindings.
Pressing that keybinding will activate the action defined in `command`.
* Anything with a `name`<sup>[1]</sup> will be added as an entry (using that
name) to the Command Palette's Action Mode.
###### Caveats
* **Nested commands** (commands with other nested commands). If a command has
nested commands in the `commands` property, AND a `keys` property, then
pressing that keybinding should open the Command Palette directly to that
level of nesting of commands.
* **"Iterable" commands** (with an `iterateOn` property): These are commands
that are expanded into one command per profile. These cannot really be bound
as keybindings - which action should be bound to the key? They can't all be
bound to the same key. If a KeyBinding/Command json blob has a valid
`iterateOn` property, then we'll ignore it as a keybinding. This includes any
commands that are nested as children of this command - we won't be able to
know which of the expanded children will be the one to bind the keys to.
<sup>[1]</sup>: This requirement will be relaxed given **Proposal 2**, below,
but ignored for the remainder of this section, for illustrative purposes.
#### Example
Consider the following settings:
```json
"bindings": [
{ "name": "Duplicate Tab", "command": "duplicateTab", "keys": "ctrl+alt+a" },
{ "command": "nextTab", "keys": "ctrl+alt+b" },
{
"icon": "...",
"name": { "key": "NewTabWithProfileRootCommandName" },
"commands": [
{
"iterateOn": "profiles",
"icon": "${profile.icon}",
"name": { "key": "NewTabWithProfileCommandName" },
"command": { "action": "newTab", "profile": "${profile.name}" }
}
]
},
{
"icon": "...",
"name": "Connect to ssh...",
"commands": [
{
"keys": "ctrl+alt+c",
"icon": "...",
"name": "first.com",
"command": { "action": "newTab", "commandline": "ssh me@first.com" }
},
{
"keys": "ctrl+alt+d",
"icon": "...",
"name": "second.com",
"command": { "action": "newTab", "commandline": "ssh me@second.com" }
}
]
}
{
"keys": "ctrl+alt+e",
"icon": "...",
"name": { "key": "SplitPaneWithProfileRootCommandName" },
"commands": [
{
"iterateOn": "profiles",
"icon": "${profile.icon}",
"name": { "key": "SplitPaneWithProfileCommandName" },
"commands": [
{
"keys": "ctrl+alt+f",
"icon": "...",
"name": { "key": "SplitPaneName" },
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "automatic" }
},
{
"icon": "...",
"name": { "key": "SplitPaneVerticalName" },
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" }
},
{
"icon": "...",
"name": { "key": "SplitPaneHorizontalName" },
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" }
}
]
}
]
}
]
```
This will generate a tree of commands as follows:
```
<Command Palette>
├─ Duplicate tab { ctrl+alt+a }
├─ New Tab With Profile...
│ ├─ Profile 1
│ ├─ Profile 2
│ └─ Profile 3
├─ Connect to ssh...
│ ├─ first.com { ctrl+alt+c }
│ └─ second.com { ctrl+alt+d }
└─ New Pane... { ctrl+alt+e }
├─ Profile 1...
| ├─ Split Automatically
| ├─ Split Vertically
| └─ Split Horizontally
├─ Profile 2...
| ├─ Split Automatically
| ├─ Split Vertically
| └─ Split Horizontally
└─ Profile 3...
├─ Split Automatically
├─ Split Vertically
└─ Split Horizontally
```
Note also the keybindings in the above example:
* <kbd>ctrl+alt+a</kbd>: This key chord is bound to the "Duplicate tab"
(`duplicateTab`) action, which is also bound to the command with the same
name.
* <kbd>ctrl+alt+b</kbd>: This key chord is bound to the `nextTab` action, which
doesn't have an associated command.
* <kbd>ctrl+alt+c</kbd>: This key chord is bound to the "Connect to
ssh../first.com" action, which will open a new tab with the `commandline`
`"ssh me@first.com"`. When the user presses this keybinding, the action will
be executed immediately, without the Command Palette appearing.
* <kbd>ctrl+alt+d</kbd>: This is the same as the above, but with the "Connect to
ssh../second.com" action.
* <kbd>ctrl+alt+e</kbd>: This key chord is bound to opening the Command Palette
to the "New Pane..." command's menu. When the user presses this keybinding,
they'll be prompted with this command's sub-commands:
```
Profile 1...
Profile 2...
Profile 3...
```
* <kbd>ctrl+alt+f</kbd>: This key will _not_ be bound to any action. The parent
action is iterable, which means that the `SplitPaneName` command is going to
get turned into one command for each and every profile, and therefore cannot
be bound to just a single action.
### Proposal 2: Automatically synthesize action names
Previously, all Commands were required to have a `name`. This name was used as
the text for the action in the Action Mode of the Command Palette. However, this
is a little tedious for users who already have lots of keys bound. They'll need
to go through and add names to each of their existing keybindings to ensure that
the actions appear in the palette. Could we instead synthesize the names for the
commands ourselves? This would enable users to automatically get each of their
existing keybindings to appear in the palette without any extra work.
To support this, the following changes will be made:
* `ActionAndArgs` will get a `GenerateName()` method defined. This will create a
string describing the `ShortcutAction` and it's associated `ActionArgs`.
- Not EVERY action _needs_ to define a result for `GenerateName`. Actions that
don't _won't_ be automatically added to the Command Palette.
- Each of the strings used in `GenerateName` will need to come from our
resources, so they can be localized appropriately.
* When we're parsing commands, if a command doesn't have a `name`, we'll instead
attempt to use `GenerateName` to create the unique string for the action
associated with this command. If the command does have a `name` set, we'll use
that string instead, allowing the user to override the default name.
- If a command has it's name set to `null`, then we'll ignore the command
entirely, not just use the generated name.
[**Appendix 1**](#appendix-1-name-generation-samples-for-ShortcutActions) below
shows a complete sample of the strings that will be generated for each of the existing
`ShortcutActions`, and many of the actions that have been proposed, but not yet
implemented.
These strings should be human-friendly versions of the actions and their
associated args. For some of these actions, with very few arguments, the strings
can be relatively simple. Take for example, `CopyText`:
JSON | Generated String
-- | --
`{ "action":"copyText" }` | "Copy text"
`{ "action":"copyText", "singleLine": true }` | "Copy text as a single line"
`{ "action":"copyText", "singleLine": false, "copyFormatting": false }` | "Copy text without formatting"
`{ "action":"copyText", "singleLine": true, "copyFormatting": true }` | "Copy text as a single line without formatting"
CopyText is a bit of a simplistic case however, with very few args or
permutations of argument values. For things like `newTab`, `splitPane`, where
there are many possible arguments and values, it will be acceptable to simply
append `", property:value"` strings to the generated names for each of the set
values.
For example:
JSON | Generated String
-- | --
`{ "action":"newTab", "profile": "Hello" }` | "Open a new tab, profile:Hello"
`{ "action":"newTab", "profile": "Hello", "directory":"C:\\", "commandline": "wsl.exe", title": "Foo" }` | "Open a new tab, profile:Hello, directory:C:\\, commandline:wsl.exe, title:Foo"
This is being chosen in favor of something that might be more human-friendly,
like "Open a new tab with profile {profile name} in {directory} with
{commandline} and a title of {title}". This string would be much harder to
synthesize, especially considering localization concerns.
#### Remove the resource key notation
Since we'll be using localized names for each of the actions in `GenerateName`,
we no longer _need_ to provide the `{ "name":{ "key": "SomeResourceKey" } }`
syntax introduced in the original spec. This functionality was used to allow us
to define localizable names for the default commands.
However, I think we should keep this functionality, to allow us additional
flexibility when defining default commands.
### Complete Defaults
Considering both of the above proposals, the default keybindings and commands
will be defined as follows:
* The current default keybindings will be untouched. These actions will
automatically be added to the Command Palette, using their names generated
from `GenerateName`.
- **TODO: FOR DISCUSSION**: Should we manually set the names for the default
"New Tab, profile index: 0" keybindings to `null`? This seems like a not
terribly helpful name for the Command Palette, especially considering the
iterable commands listed below.
* We'll add a few new commands:
- A nested, iterable command for "Open new tab with
profile..."/"Profile:{profile name}"
- A nested, iterable command for "Select color scheme..."/"{scheme name}"
- A nested, iterable command for "New Pane..."/"Profile:{profile
name}..."/["Automatic", "Horizontal", "Vertical"]
> 👉 NOTE: These default nested commands can be removed by the user defining
> `{ "name": "Open new tab with profile...", "action":null }` (et al) in their
> settings.
- If we so chose, in the future we can add further commands that we think are
helpful to `defaults.json`, without needing to give them keys. For example,
we could add
```json
{ "command": { "action": "copy", "singleLine": true } }
```
to `bindings`, to add a "copy text as a single line" command, without
necessarily binding it to a keystroke.
These changes to the `defaults.json` are represented in json as the following:
```json
"bindings": [
{
"icon": null,
"name": { "key": "NewTabWithProfileRootCommandName" },
"commands": [
{
"iterateOn": "profiles",
"icon": "${profile.icon}",
"name": "${profile.name}",
"command": { "action": "newTab", "profile": "${profile.name}" }
}
]
},
{
"icon": null,
"name": { "key": "SelectColorSchemeRootCommandName" },
"commands": [
{
"iterateOn": "schemes",
"icon": null,
"name": "${scheme.name}",
"command": { "action": "selectColorScheme", "scheme": "${scheme.name}" }
}
]
},
{
"icon": null,
"name": { "key": "SplitPaneWithProfileRootCommandName" },
"commands": [
{
"iterateOn": "profiles",
"icon": "${profile.icon}",
"name": { "key": "SplitPaneWithProfileCommandName" },
"commands": [
{
"icon": null,
"name": { "key": "SplitPaneName" },
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "automatic" }
},
{
"icon": null,
"name": { "key": "SplitPaneVerticalName" },
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" }
},
{
"icon": null,
"name": { "key": "SplitPaneHorizontalName" },
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" }
}
]
}
]
}
]
```
A complete diagram of what the default Command Palette will look like given the
default keybindings and these changes is given in [**Appendix
2**](#appendix-2-complete-default-command-palette).
## Concerns
**DISCUSSION**: "New tab with index {index}". How does this play with
the new tab dropdown customizations in [#5888]? In recent iterations of that
spec, we changed the meaning of `{ "action": "newTab", "index": 1 }` to mean
"open the first entry in the new tab menu". If that's a profile, then we'll open
a new tab with it. If it's an action, we'll perform that action. If it's a
nested menu, then we'll open the menu to that entry.
Additionally, how exactly does that play with something like `{ "action":
"newTab", "index": 1, "commandline": "wsl.exe" }`? This is really a discussion
for that spec, but is an issue highlighted by this spec. If the first entry is
anything other than a `profile`, then the `commandline` parameter doesn't really
mean anything anymore. I'm tempted to revert this particular portion of the new
tab menu customization spec over this.
We could instead add an `index` to `openNewTabDropdown`, and have that string
instead be "Open new tab dropdown, index:1". That would help disambiguate the
two.
Following discussion, it was decided that this was in fact the cleanest
solution, when accounting for both the needs of the new tab dropdown and the
command palette. The [#5888] spec has been updated to reflect this.
## Future considerations
* Some of these command names are starting to get _very_ long. Perhaps we need a
netting to display Command Palette entries on two lines (or multiple, as
necessary).
* When displaying the entries of a nested command to the user, should we display
a small label showing the name of the previous command? My gut says _yes_. In
the Proposal 1 example, pressing `ctrl+alt+e` to jump to "Split Pane..."
should probably show a small label that displays "Split Pane..." above the
list of nested commands.
* It wouldn't be totally impossible to allow keys to be bound to an iterable
command, and then simply have the key work as "open the command palette with
only the commands generated by this iterable command". This is left as a
future option, as it might require some additional technical plumbing.
## Appendix 1: Name generation samples for `ShortcutAction`s
### Current `ShortcutActions`
* `CopyText`
- "Copy text"
- "Copy text as a single line"
- "Copy text without formatting"
- "Copy text as a single line without formatting"
* `PasteText`
- "Paste text"
* `OpenNewTabDropdown`
- "Open new tab dropdown"
* `DuplicateTab`
- "Duplicate tab"
* `NewTab`
- "Open a new tab, profile:{profile name}, directory:{directory}, commandline:{commandline}, title:{title}"
* `NewWindow`
- "Open a new window"
- "Open a new window, profile:{profile name}, directory:{directory}, commandline:{commandline}, title:{title}"
* `CloseWindow`
- "Close window"
* `CloseTab`
- "Close tab"
* `ClosePane`
- "Close pane"
* `NextTab`
- "Switch to the next tab"
* `PrevTab`
- "Switch to the previous tab"
* `SplitPane`
- "Open a new pane, profile:{profile name}, split direction:{direction}, split size:{X%/Y chars}, resize parents, directory:{directory}, commandline:{commandline}, title:{title}"
- "Duplicate the current pane, split direction:{direction}, split size:{X%/Y chars}, resize parents, directory:{directory}, commandline:{commandline}, title:{title}"
* `SwitchToTab`
- "Switch to tab {index}"
* `AdjustFontSize`
- "Increase the font size"
- "Decrease the font size"
* `ResetFontSize`
- "Reset the font size"
* `ScrollUp`
- "Scroll up a line"
- "Scroll up {amount} lines"
* `ScrollDown`
- "Scroll down a line"
- "Scroll down {amount} lines"
* `ScrollUpPage`
- "Scroll up a page"
- "Scroll up {amount} pages"
* `ScrollDownPage`
- "Scroll down a page"
- "Scroll down {amount} pages"
* `ResizePane`
- "Resize pane {direction}"
- "Resize pane {direction} {percent}%"
* `MoveFocus`
- "Move focus {direction}"
* `Find`
- "Toggle the search box"
* `ToggleFullscreen`
- "Toggle fullscreen mode"
* `OpenSettings`
- "Open settings"
- "Open settings file"
- "Open default settings file"
* `ToggleCommandPalette`
- "Toggle the Command Palette"
- "Toggle the Command Palette in commandline mode"
### Other yet unimplemented actions:
* `SwitchColorScheme`
- "Select color scheme {name}"
* `ToggleRetroEffect`
- "Toggle the retro terminal effect"
* `ExecuteCommandline`
- "Run a wt commandline: {cmdline}"
* `ExecuteActions`
- OPINION: THIS ONE SHOULDN'T HAVE A NAME. We're not including any of these by
default. The user knows what they're putting in the settings by adding this
action, let them name it.
- Alternatively: "Run actions: {action.ToName() for action in actions}"
* `SendInput`
- OPINION: THIS ONE SHOULDN'T HAVE A NAME. We're not including any of these by
default. The user knows what they're putting in the settings by adding this
action, let them name it.
* `ToggleMarkMode`
- "Toggle Mark Mode"
* `NextTab`
- "Switch to the next most-recent tab"
* `SetTabColor`
- "Set the color of the current tab to {#color}"
* It would be _really_ cool if we could display a sample of the color
inline, but that's left as a future consideration.
- "Set the color for this tab..."
* this command isn't nested, but hitting enter immediately does something
with the UI, so that's _fine_
* `RenameTab`
- "Rename this tab to {name}"
- "Rename this tab..."
* this command isn't nested, but hitting enter immediately does something
with the UI, so that's _fine_
## Appendix 2: Complete Default Command Palette
This diagram shows what the default value of the Command Palette would be. This
assumes that the user has 3 profiles, "Profile 1", "Profile 2", and "Profile 3",
as well as 3 schemes: "Scheme 1", "Scheme 2", and "Scheme 3".
```
<Command Palette>
├─ Close Window
├─ Toggle fullscreen mode
├─ Open new tab dropdown
├─ Open settings
├─ Open default settings file
├─ Toggle the search box
├─ New Tab
├─ New Tab, profile index: 0
├─ New Tab, profile index: 1
├─ New Tab, profile index: 2
├─ New Tab, profile index: 3
├─ New Tab, profile index: 4
├─ New Tab, profile index: 5
├─ New Tab, profile index: 6
├─ New Tab, profile index: 7
├─ New Tab, profile index: 8
├─ Duplicate tab
├─ Switch to the next tab
├─ Switch to the previous tab
├─ Switch to tab 0
├─ Switch to tab 1
├─ Switch to tab 2
├─ Switch to tab 3
├─ Switch to tab 4
├─ Switch to tab 5
├─ Switch to tab 6
├─ Switch to tab 7
├─ Switch to tab 8
├─ Close pane
├─ Open a new pane, split: horizontal
├─ Open a new pane, split: vertical
├─ Duplicate the current pane
├─ Resize pane down
├─ Resize pane left
├─ Resize pane right
├─ Resize pane up
├─ Move focus down
├─ Move focus left
├─ Move focus right
├─ Move focus up
├─ Copy Text
├─ Paste Text
├─ Scroll down a line
├─ Scroll down a page
├─ Scroll up a line
├─ Scroll up a page
├─ Increase the font size
├─ Decrease the font size
├─ Reset the font size
├─ New Tab With Profile...
│ ├─ Profile 1
│ ├─ Profile 2
│ └─ Profile 3
├─ Select Color Scheme...
│ ├─ Scheme 1
│ ├─ Scheme 2
│ └─ Scheme 3
└─ New Pane...
├─ Profile 1...
| ├─ Split Automatically
| ├─ Split Vertically
| └─ Split Horizontally
├─ Profile 2...
| ├─ Split Automatically
| ├─ Split Vertically
| └─ Split Horizontally
└─ Profile 3...
├─ Split Automatically
├─ Split Vertically
└─ Split Horizontally
```
<!-- Footnotes -->
[Command Palette Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Command%20Palette.md

View File

@@ -7,7 +7,7 @@ This document outlines the roadmap towards delivering Windows Terminal 2.0 by Sp
## 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 been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
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).
| Duration | Activity | Releases |
| --- | --- | --- |
@@ -21,7 +21,7 @@ Below is the schedule for when milestones will be included in release builds of
| Milestone End Date | Milestone Name | Preview Release Blog Post |
| ------------------ | -------------- | ------------------------- |
| 2020-06-30 | [1.1] in Windows Terminal Preview | |
| 2020-06-18 | [1.1] in Windows Terminal Preview | [Windows Terminal Preview 1.1 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-1-release/) |
| 2020-07-31 | [1.2] in Windows Terminal Preview<br>[1.1] in Windows Terminal | |
| 2020-08-31 | 1.3 in Windows Terminal Preview<br>[1.2] in Windows Terminal | |
| 2020-09-30 | 1.4 in Windows Terminal Preview<br>1.3 in Windows Terminal | |

View File

@@ -37,7 +37,7 @@ namespace Samples.Terminal
internal ReadConsoleInputStream(HFILE handle,
BlockingCollection<Kernel32.INPUT_RECORD> nonKeyEvents)
{
Debug.Assert(handle.IsInvalid == false, "handle.IsInvalid == false");
Debug.Assert(!handle.IsInvalid, "handle.IsInvalid == false");
_handle = handle.DangerousGetHandle();
_nonKeyEvents = nonKeyEvents;
@@ -111,7 +111,7 @@ namespace Samples.Terminal
if (record.EventType == Kernel32.EVENT_TYPE.KEY_EVENT)
{
// skip key up events - if not, every key will be duped in the stream
if (record.Event.KeyEvent.bKeyDown == false) continue;
if (!record.Event.KeyEvent.bKeyDown) continue;
// pack ucs-2/utf-16le/unicode chars into position in our byte[] buffer.
var glyph = (ushort) record.Event.KeyEvent.uChar;

View File

@@ -264,15 +264,16 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
{
// 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++)
{
upperBound += _list.at(i).GetLength();
const auto curr = begin + i;
upperBound += curr->GetLength();
if (iStart >= lowerBound && iStart < upperBound)
{
const auto curr = std::next(_list.begin(), i);
// The run that we try to insert into has the same color as the new one.
// e.g.
// AAAAABBBBBBBCCC
@@ -385,7 +386,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// 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.resize(cNewRun);
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.
@@ -393,10 +394,9 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// Get the existing run that we'll be updating/manipulating.
const auto existingRun = _list.begin();
auto pExistingRunPos = existingRun;
const auto pExistingRunEnd = existingRun + _list.size();
const auto pExistingRunEnd = _list.end();
auto pInsertRunPos = newAttrs.begin();
size_t cInsertRunRemaining = newAttrs.size();
auto pNewRunPos = newRun.begin();
size_t iExistingRunCoverage = 0;
// Copy the existing run into the new buffer up to the "start index" where the new run will be injected.
@@ -410,7 +410,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
iExistingRunCoverage += pExistingRunPos->GetLength();
// Copy it to the new run buffer and advance both pointers.
*pNewRunPos++ = *pExistingRunPos++;
newRun.push_back(*pExistingRunPos++);
}
// When we get to this point, we've copied full segments from the original existing run
@@ -429,12 +429,8 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// 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.
// Copying above advanced the pointer to an empty cell beyond what we copied.
// Back up one cell so we can manipulate the final item we copied from the existing run to the new run.
pNewRunPos--;
// Fetch out the length so we can fix it up based on the below conditions.
size_t length = pNewRunPos->GetLength();
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)
@@ -449,7 +445,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// 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 (pNewRunPos->GetAttributes() == pInsertRunPos->GetAttributes())
if (newRun.back().GetAttributes() == pInsertRunPos->GetAttributes())
{
length += pInsertRunPos->GetLength();
@@ -460,18 +456,11 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
}
// We're done manipulating the length. Store it back.
pNewRunPos->SetLength(length);
// Now that we're done adjusting the last copied item, advance the pointer into a fresh/blank
// part of the new run array.
pNewRunPos++;
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, pNewRunPos);
// Advance the new run pointer into the position just after everything we copied.
pNewRunPos += cInsertRunRemaining;
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.
@@ -488,9 +477,6 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// If we still have original existing run cells remaining, copy them into the final new run.
if (pExistingRunPos != pExistingRunEnd || iExistingRunCoverage != (iEnd + 1))
{
// Back up one cell so we can inspect the most recent item copied into the new run for optimizations.
pNewRunPos--;
// 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.
@@ -514,11 +500,11 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// 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 (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
{
size_t length = pNewRunPos->GetLength();
size_t length = newRun.back().GetLength();
length += (iExistingRunCoverage - (iEnd + 1));
pNewRunPos->SetLength(length);
newRun.back().SetLength(length);
}
else
{
@@ -526,14 +512,14 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// 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
pNewRunPos++;
newRun.emplace_back();
// Copy the existing run's color information to the new run
pNewRunPos->SetAttributes(pExistingRunPos->GetAttributes());
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.
pNewRunPos->SetLength(iExistingRunCoverage - (iEnd + 1));
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.
@@ -551,34 +537,25 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// 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 (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
else if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
{
// Add the value from the existing run into the current new run position.
size_t length = pNewRunPos->GetLength();
size_t length = newRun.back().GetLength();
length += pExistingRunPos->GetLength();
pNewRunPos->SetLength(length);
newRun.back().SetLength(length);
// Advance the existing run position since we consumed its value and merged it in.
pExistingRunPos++;
}
// OK. We're done inspecting the most recently copied cell for optimizations.
pNewRunPos++;
// Now bulk copy any segments left in the original existing run
if (pExistingRunPos < pExistingRunEnd)
{
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), pNewRunPos);
// Fix up the end pointer so we know where we are for counting how much of the new run's memory space we used.
pNewRunPos += (pExistingRunEnd - pExistingRunPos);
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), std::back_inserter(newRun));
}
}
// OK, phew. We're done. Now we just need to free the existing run, store the new run in its place,
// and update the count for the correct length of the new run now that we've filled it up.
newRun.erase(pNewRunPos, newRun.end());
// 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);
return S_OK;

View File

@@ -47,7 +47,7 @@ void UnicodeStorage::Erase(const key_type key)
// - rowMap - A map of the old row IDs to the new row IDs.
// - width - The width of the new row. Remove any items that are beyond the row width.
// - Use nullopt if we're not resizing the width of the row, just renumbering the rows.
void UnicodeStorage::Remap(const std::map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
{
// Make a temporary map to hold all the new row positioning
std::unordered_map<key_type, mapped_type> newMap;

View File

@@ -55,7 +55,7 @@ public:
void Erase(const key_type key);
void Remap(const std::map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
private:
std::unordered_map<key_type, mapped_type> _map;

View File

@@ -872,7 +872,7 @@ UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
// - newRowWidth - Optional new value for the row width.
void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
{
std::map<SHORT, SHORT> rowMap;
std::unordered_map<SHORT, SHORT> rowMap;
SHORT i = 0;
for (auto& it : _storage)
{

View File

@@ -0,0 +1,349 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalApp/CascadiaSettings.h"
#include "JsonTestClass.h"
#include "TestUtils.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalAppLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class CommandTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(CommandTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ManyCommandsSameAction);
TEST_METHOD(LayerCommand);
TEST_METHOD(TestSplitPaneArgs);
TEST_METHOD(TestResourceKeyName);
TEST_METHOD(TestAutogeneratedName);
TEST_METHOD(TestLayerOnAutogeneratedName);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
};
void CommandTests::ManyCommandsSameAction()
{
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
const std::string commands1String{ R"([ { "name":"action1", "command": { "action": "copy", "singleLine": false } } ])" };
const std::string commands2String{ R"([
{ "name":"action2", "command": "paste" },
{ "name":"action3", "command": "paste" }
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
const auto commands1Json = VerifyParseSucceeded(commands1String);
const auto commands2Json = VerifyParseSucceeded(commands2String);
std::map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(1u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(2u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(4u, commands.size());
}
void CommandTests::LayerCommand()
{
// Each one of the commands in this test should layer upon the previous, overriding the action.
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
const std::string commands1String{ R"([ { "name":"action0", "command": "paste" } ])" };
const std::string commands2String{ R"([ { "name":"action0", "command": "newTab" } ])" };
const std::string commands3String{ R"([ { "name":"action0", "command": null } ])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
const auto commands1Json = VerifyParseSucceeded(commands1String);
const auto commands2Json = VerifyParseSucceeded(commands2String);
const auto commands3Json = VerifyParseSucceeded(commands3String);
std::map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
}
{
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
VERIFY_IS_NULL(command.Action().Args());
}
{
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_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);
}
{
// This last command should "unbind" the action.
auto warnings = implementation::Command::LayerJson(commands, commands3Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(0u, commands.size());
}
}
void CommandTests::TestSplitPaneArgs()
{
// This is the same as KeyBindingsTests::TestSplitPaneArgs, but with
// looking up the action and its args from a map of commands, instead
// of from keybindings.
const std::string commands0String{ R"([
{ "name": "command0", "command": { "action": "splitPane", "split": null } },
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
{ "name": "command3", "command": { "action": "splitPane", "split": "none" } },
{ "name": "command4", "command": { "action": "splitPane" } },
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
{ "name": "command6", "command": { "action": "splitPane", "split": "foo" } }
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::map<winrt::hstring, Command> commands;
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.at(L"command0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command1");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command2");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command3");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command4");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command5");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(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(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
}
void CommandTests::TestResourceKeyName()
{
// This test checks looking up a name from a resource key.
const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
// NOTE: We're relying on DuplicateTabCommandKey being defined as
// "Duplicate Tab" here. If that string changes in our resources,
// this test will break.
auto command = commands.at(L"Duplicate tab");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
}
}
void CommandTests::TestAutogeneratedName()
{
// This test ensures that we'll correctly create commands for actions
// that don't have given names, pursuant to the spec in GH#6532.
// NOTE: The keys used to look up these commands are partially generated
// from strings in our Resources.resw. If those string values should
// change, it's likely that this test will break.
const std::string commands0String{ R"([
{ "command": { "action": "splitPane", "split": null } },
{ "command": { "action": "splitPane", "split": "vertical" } },
{ "command": { "action": "splitPane", "split": "horizontal" } },
{ "command": { "action": "splitPane", "split": "none" } },
{ "command": { "action": "splitPane" } },
{ "command": { "action": "splitPane", "split": "auto" } },
{ "command": { "action": "splitPane", "split": "foo" } }
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
// There are only 3 commands here: all of the `"none"`, `"auto"`,
// `"foo"`, `null`, and <no args> bindings all generate the same action,
// which will generate just a single name for all of them.
VERIFY_ARE_EQUAL(3u, commands.size());
{
auto command = commands.at(L"Split pane");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"Split pane, direction: Vertical");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
}
{
auto command = commands.at(L"Split pane, direction: Horizontal");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
}
void CommandTests::TestLayerOnAutogeneratedName()
{
const std::string commands0String{ R"([
{ "command": { "action": "splitPane" } },
{ "name":"Split pane", "command": { "action": "splitPane", "split": "vertical" } },
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
std::map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.size());
{
auto command = commands.at(L"Split pane");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
}
}
}

View File

@@ -44,6 +44,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestStringOverload);
TEST_METHOD(TestSetTabColorArgs);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
@@ -400,6 +402,63 @@ namespace TerminalAppLocalTests
}
}
void KeyBindingsTests::TestSetTabColorArgs()
{
const std::string bindings0String{ R"([
{ "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } },
{ "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } },
{ "keys": ["ctrl+e"], "command": { "action": "setTabColor", "color": "thisStringObviouslyWontWork" } },
{ "keys": ["ctrl+f"], "command": "setTabColor" },
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TabColor());
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
VERIFY_ARE_EQUAL(static_cast<uint32_t>(til::color(0x563412)), realArgs.TabColor().Value());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
}
void KeyBindingsTests::TestStringOverload()
{
const std::string bindings0String{ R"([

View File

@@ -80,6 +80,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestTrailingCommas);
TEST_METHOD(TestCommandsAndKeybindings);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
@@ -2331,4 +2333,204 @@ namespace TerminalAppLocalTests
VERIFY_IS_TRUE(false, L"This call to LayerJson should succeed, even with the trailing comma");
}
}
void SettingsTests::TestCommandsAndKeybindings()
{
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"
}
],
"bindings": [
{ "keys": "ctrl+a", "command": { "action": "splitPane", "split": "vertical" } },
{ "name": "ctrl+b", "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": "ctrl+c", "name": "ctrl+c", "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": "ctrl+d", "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": "ctrl+e", "command": { "action": "splitPane", "split": "horizontal" } },
{ "keys": "ctrl+f", "name":null, "command": { "action": "splitPane", "split": "horizontal" } }
]
})" };
const auto guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
VerifyParseSucceeded(settingsJson);
CascadiaSettings settings{};
settings._ParseJsonString(settingsJson, false);
settings.LayerJson(settings._userSettings);
settings._ValidateSettings();
VERIFY_ARE_EQUAL(3u, settings.GetProfiles().size());
const auto profile2Guid = settings._profiles.at(2).GetGuid();
VERIFY_ARE_NOT_EQUAL(GUID{ 0 }, profile2Guid);
auto appKeyBindings = settings._globals._keybindings;
VERIFY_ARE_EQUAL(5u, appKeyBindings->_keyShortcuts.size());
// A/D, B, C, E will be in the list of commands, for 4 total.
// * A and D share the same name, so they'll only generate a single action.
// * F's name is set manually to `null`
auto commands = settings._globals.GetCommands();
VERIFY_ARE_EQUAL(4u, commands.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_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());
}
Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set.");
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_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());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_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());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_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());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_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());
}
Log::Comment(L"Now verify the commands");
{
auto command = commands.at(L"Split pane, direction: Vertical");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.Action();
VERIFY_IS_NOT_NULL(actionAndArgs);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_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());
}
{
auto command = commands.at(L"ctrl+b");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.Action();
VERIFY_IS_NOT_NULL(actionAndArgs);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_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());
}
{
auto command = commands.at(L"ctrl+c");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.Action();
VERIFY_IS_NOT_NULL(actionAndArgs);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
VERIFY_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());
}
{
auto command = commands.at(L"Split pane, direction: Horizontal");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.Action();
VERIFY_IS_NOT_NULL(actionAndArgs);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
VERIFY_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());
}
}
}

View File

@@ -60,6 +60,7 @@
<ClCompile Include="ProfileTests.cpp" />
<ClCompile Include="ColorSchemeTests.cpp" />
<ClCompile Include="KeyBindingsTests.cpp" />
<ClCompile Include="CommandTests.cpp" />
<ClCompile Include="TabTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>

View File

@@ -2,11 +2,7 @@
#include "ActionArgs.h"
#include "ActionAndArgs.h"
#include "ActionAndArgs.g.cpp"
static constexpr std::string_view ActionKey{ "action" };
// This key is reserved to remove a keybinding, instead of mapping it to an action.
static constexpr std::string_view UnboundKey{ "unbound" };
#include <LibraryResources.h>
static constexpr std::string_view CopyTextKey{ "copy" };
static constexpr std::string_view PasteTextKey{ "paste" };
@@ -33,6 +29,16 @@ static constexpr std::string_view ResizePaneKey{ "resizePane" };
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
static constexpr std::string_view FindKey{ "find" };
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
static constexpr std::string_view RenameTabKey{ "renameTab" };
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
static constexpr std::string_view ActionKey{ "action" };
// This key is reserved to remove a keybinding, instead of mapping it to an action.
static constexpr std::string_view UnboundKey{ "unbound" };
namespace winrt::TerminalApp::implementation
{
@@ -44,7 +50,7 @@ namespace winrt::TerminalApp::implementation
// the actual strings being pointed to. However, since both these strings and
// the map are all const for the lifetime of the app, we have nothing to worry
// about here.
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionNamesMap{
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionKeyNamesMap{
{ CopyTextKey, ShortcutAction::CopyText },
{ PasteTextKey, ShortcutAction::PasteText },
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
@@ -68,8 +74,13 @@ namespace winrt::TerminalApp::implementation
{ OpenSettingsKey, ShortcutAction::OpenSettings },
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
{ SplitPaneKey, ShortcutAction::SplitPane },
{ SetTabColorKey, ShortcutAction::SetTabColor },
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
{ UnboundKey, ShortcutAction::Invalid },
{ FindKey, ShortcutAction::Find }
{ FindKey, ShortcutAction::Find },
{ RenameTabKey, ShortcutAction::RenameTab },
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
{ ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline },
};
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
@@ -95,8 +106,14 @@ namespace winrt::TerminalApp::implementation
{ ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson },
{ ShortcutAction::ExecuteCommandline, winrt::TerminalApp::implementation::ExecuteCommandlineArgs::FromJson },
{ ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson },
{ ShortcutAction::SetTabColor, winrt::TerminalApp::implementation::SetTabColorArgs::FromJson },
{ ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson },
{ ShortcutAction::Invalid, nullptr },
};
@@ -111,8 +128,8 @@ namespace winrt::TerminalApp::implementation
{
// Try matching the command to one we have. If we can't find the
// action name in our list of names, let's just unbind that key.
const auto found = ActionAndArgs::ActionNamesMap.find(actionString);
return found != ActionAndArgs::ActionNamesMap.end() ? found->second : ShortcutAction::Invalid;
const auto found = ActionAndArgs::ActionKeyNamesMap.find(actionString);
return found != ActionAndArgs::ActionKeyNamesMap.end() ? found->second : ShortcutAction::Invalid;
}
// Method Description:
@@ -209,4 +226,56 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::hstring ActionAndArgs::GenerateName() const
{
// Use a magic static to initialize this map, because we won't be able
// to load the resources at _init_, only at runtime.
static const auto GeneratedActionNames = []() {
return std::unordered_map<ShortcutAction, winrt::hstring>{
{ ShortcutAction::CopyText, RS_(L"CopyTextCommandKey") },
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
{ ShortcutAction::DuplicateTab, RS_(L"DuplicateTabCommandKey") },
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
{ ShortcutAction::PrevTab, RS_(L"PrevTabCommandKey") },
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
{ ShortcutAction::ResetFontSize, RS_(L"ResetFontSizeCommandKey") },
{ ShortcutAction::ScrollUp, RS_(L"ScrollUpCommandKey") },
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
{ ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") },
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
{ ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
{ ShortcutAction::Invalid, L"" },
{ ShortcutAction::ExecuteCommandline, L"" },
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
};
}();
if (_Args)
{
auto nameFromArgs = _Args.GenerateName();
if (!nameFromArgs.empty())
{
return nameFromArgs;
}
}
const auto found = GeneratedActionNames.find(_Action);
return found != GeneratedActionNames.end() ? found->second : L"";
}
}

View File

@@ -7,11 +7,13 @@ namespace winrt::TerminalApp::implementation
{
struct ActionAndArgs : public ActionAndArgsT<ActionAndArgs>
{
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionNamesMap;
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionKeyNamesMap;
static winrt::com_ptr<ActionAndArgs> FromJson(const Json::Value& json,
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
ActionAndArgs() = default;
hstring GenerateName() const;
GETSET_PROPERTY(TerminalApp::ShortcutAction, Action, TerminalApp::ShortcutAction::Invalid);
GETSET_PROPERTY(IActionArgs, Args, nullptr);
};

View File

@@ -14,4 +14,257 @@
#include "MoveFocusArgs.g.cpp"
#include "AdjustFontSizeArgs.g.cpp"
#include "SplitPaneArgs.g.cpp"
#include "ExecuteCommandlineArgs.g.cpp"
#include "OpenSettingsArgs.g.cpp"
#include "SetTabColorArgs.g.cpp"
#include "RenameTabArgs.g.cpp"
#include <LibraryResources.h>
namespace winrt::TerminalApp::implementation
{
winrt::hstring NewTerminalArgs::GenerateName() const
{
std::wstringstream ss;
if (!_Profile.empty())
{
ss << fmt::format(L"profile: {}, ", _Profile);
}
else if (_ProfileIndex)
{
ss << fmt::format(L"profile index: {}, ", _ProfileIndex.Value());
}
if (!_Commandline.empty())
{
ss << fmt::format(L"commandline: {}, ", _Commandline);
}
if (!_StartingDirectory.empty())
{
ss << fmt::format(L"directory: {}, ", _StartingDirectory);
}
if (!_TabTitle.empty())
{
ss << fmt::format(L"title: {}, ", _TabTitle);
}
auto s = ss.str();
if (s.empty())
{
return L"";
}
// Chop off the last ", "
return winrt::hstring{ s.substr(0, s.size() - 2) };
}
winrt::hstring CopyTextArgs::GenerateName() const
{
if (_SingleLine)
{
return RS_(L"CopyTextAsSingleLineCommandKey");
}
return RS_(L"CopyTextCommandKey");
}
winrt::hstring NewTabArgs::GenerateName() const
{
winrt::hstring newTerminalArgsStr;
if (_TerminalArgs)
{
newTerminalArgsStr = _TerminalArgs.GenerateName();
}
if (newTerminalArgsStr.empty())
{
return RS_(L"NewTabCommandKey");
}
return winrt::hstring{
fmt::format(L"{}, {}", RS_(L"NewTabCommandKey"), newTerminalArgsStr)
};
}
winrt::hstring SwitchToTabArgs::GenerateName() const
{
return winrt::hstring{
fmt::format(L"{}, index:{}", RS_(L"SwitchToTabCommandKey"), _TabIndex)
};
}
winrt::hstring ResizePaneArgs::GenerateName() const
{
winrt::hstring directionString;
switch (_Direction)
{
case Direction::Left:
directionString = RS_(L"DirectionLeft");
break;
case Direction::Right:
directionString = RS_(L"DirectionRight");
break;
case Direction::Up:
directionString = RS_(L"DirectionUp");
break;
case Direction::Down:
directionString = RS_(L"DirectionDown");
break;
}
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"ResizePaneWithArgCommandKey")),
directionString)
};
}
winrt::hstring MoveFocusArgs::GenerateName() const
{
winrt::hstring directionString;
switch (_Direction)
{
case Direction::Left:
directionString = RS_(L"DirectionLeft");
break;
case Direction::Right:
directionString = RS_(L"DirectionRight");
break;
case Direction::Up:
directionString = RS_(L"DirectionUp");
break;
case Direction::Down:
directionString = RS_(L"DirectionDown");
break;
}
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"MoveFocusWithArgCommandKey")),
directionString)
};
}
winrt::hstring AdjustFontSizeArgs::GenerateName() const
{
// If the amount is just 1 (or -1), we'll just return "Increase font
// size" (or "Decrease font size"). If the amount delta has a greater
// absolute value, we'll include it like"
// * Decrease font size, amount: {delta}"
if (_Delta < 0)
{
return _Delta == -1 ? RS_(L"DecreaseFontSizeCommandKey") :
winrt::hstring{
fmt::format(std::wstring_view(RS_(L"DecreaseFontSizeWithAmountCommandKey")),
-_Delta)
};
}
else
{
return _Delta == 1 ? RS_(L"IncreaseFontSizeCommandKey") :
winrt::hstring{
fmt::format(std::wstring_view(RS_(L"IncreaseFontSizeWithAmountCommandKey")),
_Delta)
};
}
}
winrt::hstring SplitPaneArgs::GenerateName() const
{
// The string will be similar to the following:
// * "Duplicate pane[, split: <direction>][, new terminal arguments...]"
// * "Split pane[, split: <direction>][, new terminal arguments...]"
//
// Direction will only be added to the string if the split direction is
// not "auto".
// If this is a "duplicate pane" action, then the new terminal arguments
// will be omitted (as they're unused)
std::wstringstream ss;
if (_SplitMode == SplitType::Duplicate)
{
ss << std::wstring_view(RS_(L"DuplicatePaneCommandKey"));
}
else
{
ss << std::wstring_view(RS_(L"SplitPaneCommandKey"));
}
ss << L", ";
// This text is intentionally _not_ localized, to attempt to mirror the
// exact syntax that the property would have in JSON.
switch (_SplitStyle)
{
case SplitState::Vertical:
ss << L"split: vertical, ";
break;
case SplitState::Horizontal:
ss << L"split: horizontal, ";
break;
}
winrt::hstring newTerminalArgsStr;
if (_TerminalArgs)
{
newTerminalArgsStr = _TerminalArgs.GenerateName();
}
if (_SplitMode != SplitType::Duplicate && !newTerminalArgsStr.empty())
{
ss << newTerminalArgsStr.c_str();
ss << L", ";
}
// Chop off the last ", "
auto s = ss.str();
return winrt::hstring{ s.substr(0, s.size() - 2) };
}
winrt::hstring OpenSettingsArgs::GenerateName() const
{
switch (_Target)
{
case SettingsTarget::DefaultsFile:
return RS_(L"OpenDefaultSettingsCommandKey");
case SettingsTarget::AllFiles:
return RS_(L"OpenBothSettingsFilesCommandKey");
default:
return RS_(L"OpenSettingsCommandKey");
}
}
winrt::hstring ExecuteCommandlineArgs::GenerateName() const
{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"ExecuteCommandlineCommandKey")),
_Commandline)
};
}
winrt::hstring SetTabColorArgs::GenerateName() const
{
// "Set tab color to #RRGGBB"
// "Reset tab color"
if (_TabColor)
{
til::color c{ _TabColor.Value() };
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"SetTabColorCommandKey")),
c.ToHexString(true))
};
}
return RS_(L"ResetTabColorCommandKey");
}
winrt::hstring RenameTabArgs::GenerateName() const
{
// "Rename tab to \"{_Title}\""
// "Reset tab title"
if (!_Title.empty())
{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"RenameTabCommandKey")),
_Title.c_str())
};
}
return RS_(L"ResetTabNameCommandKey");
}
}

View File

@@ -14,10 +14,14 @@
#include "MoveFocusArgs.g.h"
#include "AdjustFontSizeArgs.g.h"
#include "SplitPaneArgs.g.h"
#include "ExecuteCommandlineArgs.g.h"
#include "OpenSettingsArgs.g.h"
#include "SetTabColorArgs.g.h"
#include "RenameTabArgs.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include "Utils.h"
#include "JsonUtils.h"
#include "TerminalWarnings.h"
// Notes on defining ActionArgs and ActionEventArgs:
@@ -56,6 +60,8 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view ProfileKey{ "profile" };
public:
hstring GenerateName() const;
bool Equals(const winrt::TerminalApp::NewTerminalArgs& other)
{
return other.Commandline() == _Commandline &&
@@ -100,6 +106,8 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view SingleLineKey{ "singleLine" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<CopyTextArgs>();
@@ -127,6 +135,8 @@ namespace winrt::TerminalApp::implementation
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<NewTabArgs>();
@@ -153,6 +163,8 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view TabIndexKey{ "index" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<SwitchToTabArgs>();
@@ -217,6 +229,8 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view DirectionKey{ "direction" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<ResizePaneArgs>();
@@ -253,6 +267,8 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view DirectionKey{ "direction" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<MoveFocusArgs>();
@@ -289,6 +305,8 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view AdjustFontSizeDelta{ "delta" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<AdjustFontSizeArgs>();
@@ -355,13 +373,16 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view SplitModeKey{ "splitMode" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<SplitPaneArgs>();
if (otherAsUs)
{
return otherAsUs->_SplitStyle == _SplitStyle &&
otherAsUs->_TerminalArgs == _TerminalArgs &&
(otherAsUs->_TerminalArgs ? otherAsUs->_TerminalArgs.Equals(_TerminalArgs) :
otherAsUs->_TerminalArgs == _TerminalArgs) &&
otherAsUs->_SplitMode == _SplitMode;
}
return false;
@@ -383,6 +404,37 @@ namespace winrt::TerminalApp::implementation
}
};
struct ExecuteCommandlineArgs : public ExecuteCommandlineArgsT<ExecuteCommandlineArgs>
{
ExecuteCommandlineArgs() = default;
GETSET_PROPERTY(winrt::hstring, Commandline, false);
static constexpr std::string_view CommandlineKey{ "commandline" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<ExecuteCommandlineArgs>();
if (otherAsUs)
{
return otherAsUs->_Commandline == _Commandline;
}
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<ExecuteCommandlineArgs>();
if (auto commandline{ json[JsonKey(CommandlineKey)] })
{
args->_Commandline = winrt::to_hstring(commandline.asString());
}
return { *args, {} };
}
};
// Possible SettingsTarget values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view SettingsFileString{ "settingsFile" };
@@ -421,6 +473,8 @@ namespace winrt::TerminalApp::implementation
static constexpr std::string_view TargetKey{ "target" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<OpenSettingsArgs>();
@@ -441,6 +495,74 @@ namespace winrt::TerminalApp::implementation
return { *args, {} };
}
};
struct SetTabColorArgs : public SetTabColorArgsT<SetTabColorArgs>
{
SetTabColorArgs() = default;
GETSET_PROPERTY(Windows::Foundation::IReference<uint32_t>, TabColor, nullptr);
static constexpr std::string_view ColorKey{ "color" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<SetTabColorArgs>();
if (otherAsUs)
{
return otherAsUs->_TabColor == _TabColor;
}
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<SetTabColorArgs>();
std::optional<til::color> temp;
try
{
::TerminalApp::JsonUtils::GetOptionalColor(json, ColorKey, temp);
if (temp.has_value())
{
args->_TabColor = static_cast<uint32_t>(temp.value());
}
}
CATCH_LOG();
return { *args, {} };
}
};
struct RenameTabArgs : public RenameTabArgsT<RenameTabArgs>
{
RenameTabArgs() = default;
GETSET_PROPERTY(winrt::hstring, Title, L"");
static constexpr std::string_view TitleKey{ "title" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<RenameTabArgs>();
if (otherAsUs)
{
return otherAsUs->_Title == _Title;
}
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<RenameTabArgs>();
if (auto title{ json[JsonKey(TitleKey)] })
{
args->_Title = winrt::to_hstring(title.asString());
}
return { *args, {} };
}
};
}
namespace winrt::TerminalApp::factory_implementation

View File

@@ -6,6 +6,7 @@ namespace TerminalApp
interface IActionArgs
{
Boolean Equals(IActionArgs other);
String GenerateName();
};
interface IActionEventArgs
@@ -53,7 +54,9 @@ namespace TerminalApp
// ProfileIndex can be null (for "use the default"), so this needs to be
// a IReference, so it's nullable
Windows.Foundation.IReference<Int32> ProfileIndex { get; };
Boolean Equals(NewTerminalArgs other);
String GenerateName();
};
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
@@ -98,8 +101,23 @@ namespace TerminalApp
SplitType SplitMode { get; };
};
[default_interface] runtimeclass ExecuteCommandlineArgs : IActionArgs
{
String Commandline;
}
[default_interface] runtimeclass OpenSettingsArgs : IActionArgs
{
SettingsTarget Target { get; };
};
[default_interface] runtimeclass SetTabColorArgs : IActionArgs
{
Windows.Foundation.IReference<UInt32> TabColor { get; };
};
[default_interface] runtimeclass RenameTabArgs : IActionArgs
{
String Title { get; };
};
}

View File

@@ -237,4 +237,115 @@ namespace winrt::TerminalApp::implementation
ToggleFullscreen();
args.Handled(true);
}
void TerminalPage::_HandleToggleCommandPalette(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
// TODO GH#6677: When we add support for commandline mode, first set the
// mode that the command palette should be in, before making it visible.
CommandPalette().Visibility(CommandPalette().Visibility() == Visibility::Visible ?
Visibility::Collapsed :
Visibility::Visible);
args.Handled(true);
}
void TerminalPage::_HandleSetTabColor(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
std::optional<til::color> tabColor;
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::SetTabColorArgs>())
{
if (realArgs.TabColor() != nullptr)
{
tabColor = realArgs.TabColor().Value();
}
}
auto activeTab = _GetFocusedTab();
if (activeTab)
{
if (tabColor.has_value())
{
activeTab->SetTabColor(tabColor.value());
}
else
{
activeTab->ResetTabColor();
}
}
args.Handled(true);
}
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
auto activeTab = _GetFocusedTab();
if (activeTab)
{
activeTab->ActivateColorPicker();
}
args.Handled(true);
}
void TerminalPage::_HandleRenameTab(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
std::optional<winrt::hstring> title;
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::RenameTabArgs>())
{
title = realArgs.Title();
}
auto activeTab = _GetFocusedTab();
if (activeTab)
{
if (title.has_value())
{
activeTab->SetTabText(title.value());
}
else
{
activeTab->ResetTabText();
}
}
args.Handled(true);
}
void TerminalPage::_HandleExecuteCommandline(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& actionArgs)
{
if (const auto& realArgs = actionArgs.ActionArgs().try_as<TerminalApp::ExecuteCommandlineArgs>())
{
// Convert the commandline into an array of args with
// CommandLineToArgvW, similar to how the app typically does when
// called from the commandline.
int argc = 0;
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(realArgs.Commandline().c_str(), &argc) };
if (argv)
{
std::vector<winrt::hstring> args;
// Make sure the first argument is wt.exe, because ParseArgs
// will always skip the program name.
args.emplace_back(L"wt.exe");
for (auto& elem : wil::make_range(argv.get(), argc))
{
args.emplace_back(elem);
}
winrt::array_view<const winrt::hstring> argsView{ args };
::TerminalApp::AppCommandlineArgs appArgs;
bool handled = appArgs.ParseArgs(argsView) == 0;
if (handled)
{
_startupActions = appArgs.GetStartupActions();
_ProcessStartupActions(false);
}
actionArgs.Handled(handled);
}
}
}
}

View File

@@ -637,3 +637,52 @@ std::optional<winrt::TerminalApp::LaunchMode> AppCommandlineArgs::GetLaunchMode(
{
return _launchMode;
}
// Method Description:
// - Attempts to parse an array of commandline args into a list of
// commands to execute, and then parses these commands. As commands are
// successfully parsed, they will generate ShortcutActions for us to be
// able to execute. If we fail to parse any commands, we'll return the
// error code from the failure to parse that command, and stop processing
// additional commands.
// - The first arg in args should be the program name "wt" (or some variant). It
// will be ignored during parsing.
// Arguments:
// - args: an array of strings to process as a commandline. These args can contain spaces
// Return Value:
// - 0 if the commandline was successfully parsed
int AppCommandlineArgs::ParseArgs(winrt::array_view<const winrt::hstring>& args)
{
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
for (auto& cmdBlob : commands)
{
// On one hand, it seems like we should be able to have one
// AppCommandlineArgs for parsing all of them, and collect the
// results one at a time.
//
// On the other hand, re-using a CLI::App seems to leave state from
// previous parsings around, so we could get mysterious behavior
// where one command affects the values of the next.
//
// From https://cliutils.github.io/CLI11/book/chapters/options.html:
// > If that option is not given, CLI11 will not touch the initial
// > value. This allows you to set up defaults by simply setting
// > your value beforehand.
//
// So we pretty much need the to either manually reset the state
// each command, or build new ones.
const auto result = ParseCommand(cmdBlob);
// If this succeeded, result will be 0. Otherwise, the caller should
// exit(result), to exit the program.
if (result != 0)
{
return result;
}
}
// If all the args were successfully parsed, we'll have some commands
// built in _appArgs, which we'll use when the application starts up.
return 0;
}

View File

@@ -28,7 +28,9 @@ public:
AppCommandlineArgs();
~AppCommandlineArgs() = default;
int ParseCommand(const Commandline& command);
int ParseArgs(winrt::array_view<const winrt::hstring>& args);
static std::vector<Commandline> BuildCommands(const std::vector<const wchar_t*>& args);
static std::vector<Commandline> BuildCommands(winrt::array_view<const winrt::hstring>& args);

View File

@@ -55,8 +55,11 @@ namespace winrt::TerminalApp::implementation
{
for (auto& kv : _keyShortcuts)
{
if (kv.second.Action() == actionAndArgs.Action() &&
kv.second.Args().Equals(actionAndArgs.Args()))
const auto action = kv.second.Action();
const auto args = kv.second.Args();
const auto actionMatched = action == actionAndArgs.Action();
const auto argsMatched = args ? args.Equals(actionAndArgs.Args()) : args == actionAndArgs.Args();
if (actionMatched && argsMatched)
{
return kv.first;
}

View File

@@ -64,7 +64,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
// Iterate over all the possible actions in the names list, and see if
// it has a binding.
for (auto& actionName : ActionAndArgs::ActionNamesMap)
for (auto& actionName : ActionAndArgs::ActionKeyNamesMap)
{
const auto searchedForName = actionName.first;
const auto searchedForAction = actionName.second;
@@ -85,7 +85,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
// - Deserialize an AppKeyBindings from the key mappings that are in the array
// `json`. The json array should contain an array of objects with both a
// `command` string and a `keys` array, where `command` is one of the names
// listed in `ActionAndArgs::ActionNamesMap`, and `keys` is an array of
// listed in `ActionAndArgs::ActionKeyNamesMap`, and `keys` is an array of
// keypresses. Currently, the array should contain a single string, which can
// be deserialized into a KeyChord.
// - Applies the deserialized keybindings to the provided `bindings` object. If

View File

@@ -472,7 +472,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - a point containing the requested dimensions in pixels.
winrt::Windows::Foundation::Point AppLogic::GetLaunchDimensions(uint32_t dpi)
winrt::Windows::Foundation::Size AppLogic::GetLaunchDimensions(uint32_t dpi)
{
if (!_loadedInitialSettings)
{
@@ -502,7 +502,7 @@ namespace winrt::TerminalApp::implementation
// of the height calculation here.
auto titlebar = TitlebarControl{ static_cast<uint64_t>(0) };
titlebar.Measure({ SHRT_MAX, SHRT_MAX });
proposedSize.Y += (titlebar.DesiredSize().Height) * scale;
proposedSize.Height += (titlebar.DesiredSize().Height) * scale;
}
else if (_settings->GlobalSettings().AlwaysShowTabs())
{
@@ -517,7 +517,7 @@ namespace winrt::TerminalApp::implementation
// For whatever reason, there's about 6px of unaccounted-for space
// in the application. I couldn't tell you where these 6px are
// coming from, but they need to be included in this math.
proposedSize.Y += (tabControl.DesiredSize().Height + 6) * scale;
proposedSize.Width += (tabControl.DesiredSize().Height + 6) * scale;
}
return proposedSize;
@@ -972,7 +972,7 @@ namespace winrt::TerminalApp::implementation
// or 0. (see AppLogic::_ParseArgs)
int32_t AppLogic::SetStartupCommandline(array_view<const winrt::hstring> args)
{
const auto result = _ParseArgs(args);
const auto result = _appArgs.ParseArgs(args);
if (result == 0)
{
_appArgs.ValidateStartupCommands();
@@ -982,53 +982,6 @@ namespace winrt::TerminalApp::implementation
return result;
}
// Method Description:
// - Attempts to parse an array of commandline args into a list of
// commands to execute, and then parses these commands. As commands are
// successfully parsed, they will generate ShortcutActions for us to be
// able to execute. If we fail to parse any commands, we'll return the
// error code from the failure to parse that command, and stop processing
// additional commands.
// Arguments:
// - args: an array of strings to process as a commandline. These args can contain spaces
// Return Value:
// - 0 if the commandline was successfully parsed
int AppLogic::_ParseArgs(winrt::array_view<const hstring>& args)
{
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
for (auto& cmdBlob : commands)
{
// On one hand, it seems like we should be able to have one
// AppCommandlineArgs for parsing all of them, and collect the
// results one at a time.
//
// On the other hand, re-using a CLI::App seems to leave state from
// previous parsings around, so we could get mysterious behavior
// where one command affects the values of the next.
//
// From https://cliutils.github.io/CLI11/book/chapters/options.html:
// > If that option is not given, CLI11 will not touch the initial
// > value. This allows you to set up defaults by simply setting
// > your value beforehand.
//
// So we pretty much need the to either manually reset the state
// each command, or build new ones.
const auto result = _appArgs.ParseCommand(cmdBlob);
// If this succeeded, result will be 0. Otherwise, the caller should
// exit(result), to exit the program.
if (result != 0)
{
return result;
}
}
// If all the args were successfully parsed, we'll have some commands
// built in _appArgs, which we'll use when the application starts up.
return 0;
}
// Method Description:
// - If there were any errors parsing the commandline that was used to
// initialize the terminal, this will return a string containing that

View File

@@ -34,7 +34,7 @@ namespace winrt::TerminalApp::implementation
winrt::hstring ApplicationDisplayName() const;
winrt::hstring ApplicationVersion() const;
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi);
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();

View File

@@ -41,7 +41,7 @@ namespace TerminalApp
String ApplicationDisplayName { get; };
String ApplicationVersion { get; };
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi);
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();

View File

@@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Command.h"
#include "Command.g.cpp"
#include "Utils.h"
#include "ActionAndArgs.h"
#include <LibraryResources.h>
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::TerminalApp;
static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view IconPathKey{ "iconPath" };
static constexpr std::string_view ActionKey{ "command" };
static constexpr std::string_view ArgsKey{ "args" };
namespace winrt::TerminalApp::implementation
{
// Function Description:
// - attempt to get the name of this command from the provided json object.
// * If the "name" property is a string, return that value.
// * If the "name" property is an object, attempt to lookup the string
// resource specified by the "key" property, to support localizable
// command names.
// Arguments:
// - json: The Json::Value representing the command object we should get the name for.
// Return Value:
// - the empty string if we couldn't find a name, otherwise the command's name.
static winrt::hstring _nameFromJson(const Json::Value& json)
{
if (const auto name{ json[JsonKey(NameKey)] })
{
if (name.isObject())
{
try
{
if (const auto keyJson{ name[JsonKey("key")] })
{
// Make sure the key is present before we try
// loading it. Otherwise we'll crash
const auto resourceKey = GetWstringFromJson(keyJson);
if (HasLibraryResourceWithName(resourceKey))
{
return GetLibraryResourceString(resourceKey);
}
}
}
CATCH_LOG();
}
else if (name.isString())
{
auto nameStr = name.asString();
return winrt::to_hstring(nameStr);
}
}
return L"";
}
// Method Description:
// - Get the name for the command specified in `json`. If there is no "name"
// property in the provided json object, then instead generate a name for
// the provided ActionAndArgs.
// Arguments:
// - json: json for the command to generate a name for.
// - actionAndArgs: An ActionAndArgs object to use to generate a name for,
// if the json object doesn't contain a "name".
// Return Value:
// - The "name" from the json, or the generated name from ActionAndArgs::GenerateName
static winrt::hstring _nameFromJsonOrAction(const Json::Value& json,
winrt::com_ptr<ActionAndArgs> actionAndArgs)
{
auto manualName = _nameFromJson(json);
if (!manualName.empty())
{
return manualName;
}
if (!actionAndArgs)
{
return L"";
}
return actionAndArgs->GenerateName();
}
// Method Description:
// - Deserialize a Command from the `json` object. The json object should
// contain a "name" and "action", and optionally an "icon".
// * "name": string|object - the name of the command to display in the
// command palette. If this is an object, look for the "key" property,
// and try to load the string from our resources instead.
// * "action": string|object - A ShortcutAction, either as a name or as an
// ActionAndArgs serialization. See ActionAndArgs::FromJson for details.
// If this is null, we'll remove this command from the list of commands.
// Arguments:
// - json: the Json::Value to deserialize into a Command
// - warnings: If there were any warnings during parsing, they'll be
// appended to this vector.
// Return Value:
// - the newly constructed Command object.
winrt::com_ptr<Command> Command::FromJson(const Json::Value& json,
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
{
auto result = winrt::make_self<Command>();
// TODO GH#6644: iconPath not implemented quite yet. Can't seem to get
// the binding quite right. Additionally, do we want it to be an image,
// or a FontIcon? I've had difficulty binding either/or.
if (const auto actionJson{ json[JsonKey(ActionKey)] })
{
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
if (actionAndArgs)
{
result->_setAction(*actionAndArgs);
}
else
{
// Something like
// { name: "foo", action: "unbound" }
// will _remove_ the "foo" command, by returning null here.
return nullptr;
}
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
}
else
{
// { name: "foo", action: null } will land in this case, which
// should also be used for unbinding.
return nullptr;
}
if (result->_Name.empty())
{
return nullptr;
}
return result;
}
// Function Description:
// - Attempt to parse all the json objects in `json` into new Command
// objects, and add them to the map of commands.
// - If any parsed command has
// the same Name as an existing command in commands, the new one will
// layer on top of the existing one.
// Arguments:
// - commands: a map of Name->Command which new commands should be layered upon.
// - json: A Json::Value containing an array of serialized commands
// Return Value:
// - A vector containing any warnings detected while parsing
std::vector<::TerminalApp::SettingsLoadWarnings> Command::LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
const Json::Value& json)
{
std::vector<::TerminalApp::SettingsLoadWarnings> warnings;
for (const auto& value : json)
{
if (value.isObject())
{
try
{
auto result = Command::FromJson(value, warnings);
if (result)
{
// Override commands with the same name
commands.insert_or_assign(result->Name(), *result);
}
else
{
// If there wasn't a parsed command, then try to get the
// name from the json blob. If that name currently
// exists in our list of commands, we should remove it.
const auto name = _nameFromJson(value);
if (!name.empty())
{
commands.erase(name);
}
}
}
CATCH_LOG();
}
}
return warnings;
}
}

View File

@@ -0,0 +1,45 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- Command.h
Abstract:
- A command represents a single entry in the Command Palette. This is an object
that has a user facing "name" to display to the user, and an associated action
which can be dispatched.
- For more information, see GH#2046, #5400, #5674, and #6635
Author(s):
- Mike Griese - June 2020
--*/
#pragma once
#include "Command.g.h"
#include "TerminalWarnings.h"
#include "..\inc\cppwinrt_utils.h"
namespace winrt::TerminalApp::implementation
{
struct Command : CommandT<Command>
{
Command() = default;
static winrt::com_ptr<Command> FromJson(const Json::Value& json, std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
const Json::Value& json);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers);
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(Command);
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "../ShortcutActionDispatch.idl";
namespace TerminalApp
{
[default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
Command();
String Name;
ActionAndArgs Action;
String KeyChordText;
}
}

View File

@@ -0,0 +1,480 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "CommandPalette.h"
#include "ActionArgs.h"
#include "ActionAndArgs.h"
#include "CommandPalette.g.cpp"
#include <winrt/Microsoft.Terminal.Settings.h>
using namespace winrt;
using namespace winrt::TerminalApp;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::System;
using namespace winrt::Windows::Foundation;
namespace winrt::TerminalApp::implementation
{
CommandPalette::CommandPalette()
{
InitializeComponent();
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
_allActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
if (CommandPaletteShadow())
{
// Hook up the shadow on the command palette to the backdrop that
// will actually show it. This needs to be done at runtime, and only
// if the shadow actually exists. ThemeShadow isn't supported below
// version 18362.
CommandPaletteShadow().Receivers().Append(_shadowBackdrop());
// "raise" the command palette up by 16 units, so it will cast a shadow.
_backdrop().Translation({ 0, 0, 16 });
}
// Whatever is hosting us will enable us by setting our visibility to
// "Visible". When that happens, set focus to our search box.
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
if (Visibility() == Visibility::Visible)
{
_searchBox().Focus(FocusState::Programmatic);
_filteredActionsView().SelectedIndex(0);
}
else
{
// Raise an event to return control to the Terminal.
_close();
}
});
}
// Method Description:
// - Moves the focus up or down the list of commands. If we're at the top,
// we'll loop around to the bottom, and vice-versa.
// Arguments:
// - moveDown: if true, we're attempting to move to the next item in the
// list. Otherwise, we're attempting to move to the previous.
// Return Value:
// - <none>
void CommandPalette::_selectNextItem(const bool moveDown)
{
const auto selected = _filteredActionsView().SelectedIndex();
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
// Wraparound math. By adding numItems and then calculating modulo numItems,
// we clamp the values to the range [0, numItems) while still supporting moving
// upward from 0 to numItems - 1.
const auto newIndex = ((numItems + selected + (moveDown ? 1 : -1)) % numItems);
_filteredActionsView().SelectedIndex(newIndex);
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
}
// Method Description:
// - Process keystrokes in the input box. This is used for moving focus up
// and down the list of commands in Action mode, and for executing
// commands in both Action mode and Commandline mode.
// Arguments:
// - e: the KeyRoutedEventArgs containing info about the keystroke.
// Return Value:
// - <none>
void CommandPalette::_keyDownHandler(IInspectable const& /*sender*/,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto key = e.OriginalKey();
if (key == VirtualKey::Up)
{
// Action Mode: Move focus to the next item in the list.
_selectNextItem(false);
e.Handled(true);
}
else if (key == VirtualKey::Down)
{
// Action Mode: Move focus to the previous item in the list.
_selectNextItem(true);
e.Handled(true);
}
else if (key == VirtualKey::Enter)
{
if (_mode == PaletteMode::ActionMode)
{
// Action Mode: Dispatch the action of the selected command.
if (const auto selectedItem = _filteredActionsView().SelectedItem())
{
_dispatchAction();
}
}
else
{
_dispatchCommandline();
}
e.Handled(true);
}
else if (key == VirtualKey::Escape)
{
// Action Mode: Dismiss the palette if the text is empty, otherwise clear the search string.
if (_searchBox().Text().empty())
{
_close();
}
else
{
_searchBox().Text(L"");
}
e.Handled(true);
}
}
// Method Description:
// - This event is triggered when someone clicks anywhere in the bounds of
// the window that's _not_ the command palette UI. When that happens,
// we'll want to dismiss the palette.
// Arguments:
// - <unused>
// Return Value:
// - <none>
void CommandPalette::_rootPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
{
_close();
}
// Method Description:
// - This event is only triggered when someone clicks in the space right
// next to the text box in the command palette. We _don't_ want that click
// to light dismiss the palette, so we'll mark it handled here.
// Arguments:
// - e: the PointerRoutedEventArgs that we want to mark as handled
// Return Value:
// - <none>
void CommandPalette::_backdropPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e)
{
e.Handled(true);
}
// Method Description:
// - This event is called when the user clicks on an individual item from
// the list. We'll get the item that was clicked and dispatch the command
// that the user clicked on.
// Arguments:
// - e: an ItemClickEventArgs who's ClickedItem() will be the command that was clicked on.
// Return Value:
// - <none>
void CommandPalette::_listItemClicked(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::Controls::ItemClickEventArgs const& e)
{
if (auto command{ e.ClickedItem().try_as<TerminalApp::Command>() })
{
const auto actionAndArgs = command.Action();
_dispatch.DoAction(actionAndArgs);
_close();
}
}
// Method Description:
// - Event handler for when the text in the input box changes. In Action
// Mode, we'll update the list of displayed commands, and select the first one.
// Arguments:
// - <unused>
// Return Value:
// - <none>
void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/,
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
{
_checkMode();
_updateFilteredActions();
_filteredActionsView().SelectedIndex(0);
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
}
Collections::IObservableVector<Command> CommandPalette::FilteredActions()
{
return _filteredActions;
}
void CommandPalette::SetActions(Collections::IVector<TerminalApp::Command> const& actions)
{
_allActions = actions;
_updateFilteredActions();
}
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
static bool _compareCommandNames(const TerminalApp::Command& lhs, const TerminalApp::Command& rhs)
{
std::wstring_view leftName{ lhs.Name() };
std::wstring_view rightName{ rhs.Name() };
return leftName.compare(rightName) < 0;
}
// This is a helper struct to aid in sorting Commands by a given weighting.
struct WeightedCommand
{
TerminalApp::Command command;
int weight;
bool operator<(const WeightedCommand& other) const
{
// If two commands have the same weight, then we'll sort them alphabetically.
if (weight == other.weight)
{
return !_compareCommandNames(command, other.command);
}
return weight < other.weight;
}
};
// Method Description:
// - Update our list of filtered actions to reflect the current contents of
// the input box. For more details on which commands will be displayed,
// see `_getWeight`.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CommandPalette::_updateFilteredActions()
{
_filteredActions.Clear();
if (_mode == PaletteMode::CommandlineMode)
{
return;
}
auto searchText = _searchBox().Text();
const bool addAll = searchText.empty();
// If there's no filter text, then just add all the commands in order to the list.
// - TODO GH#6647:Possibly add the MRU commands first in order, followed
// by the rest of the commands.
if (addAll)
{
// Add all the commands, but make sure they're sorted alphabetically.
std::vector<TerminalApp::Command> sortedCommands;
sortedCommands.reserve(_allActions.Size());
for (auto action : _allActions)
{
sortedCommands.push_back(action);
}
std::sort(sortedCommands.begin(),
sortedCommands.end(),
_compareCommandNames);
for (auto action : sortedCommands)
{
_filteredActions.Append(action);
}
return;
}
// Here, there was some filter text.
// Show these actions in a weighted order.
// - Matching the first character of a word, then the first char of a
// subsequent word seems better than just "the order they appear in
// the list".
// - TODO GH#6647:"Recently used commands" ordering also seems valuable.
// * This could be done by weighting the recently used commands
// higher the more recently they were used, then weighting all
// the unused commands as 1
// Use a priority queue to order commands so that "better" matches
// appear first in the list. The ordering will be determined by the
// match weight produced by _getWeight.
std::priority_queue<WeightedCommand> heap;
for (auto action : _allActions)
{
const auto weight = CommandPalette::_getWeight(searchText, action.Name());
if (weight > 0)
{
WeightedCommand wc;
wc.command = action;
wc.weight = weight;
heap.push(wc);
}
}
// At this point, all the commands in heap are matches. We've also
// sorted commands with the same weight alphabetically.
// Remove everything in-order from the queue, and add to the list of
// filtered actions.
while (!heap.empty())
{
auto top = heap.top();
heap.pop();
_filteredActions.Append(top.command);
}
}
// Function Description:
// - Calculates a "weighting" by which should be used to order a command
// name relative to other names, given a specific search string.
// Currently, this is based off of two factors:
// * The weight is incremented once for each matched character of the
// search text.
// * If a matching character from the search text was found at the start
// of a word in the name, then we increment the weight again.
// * For example, for a search string "sp", we want "Split Pane" to
// appear in the list before "Close Pane"
// * Consecutive matches will be weighted higher than matches with
// characters in between the search characters.
// - This will return 0 if the command should not be shown. If all the
// characters of search text appear in order in `name`, then this function
// will return a positive number. There can be any number of characters
// separating consecutive characters in searchText.
// * For example:
// "name": "New Tab"
// "name": "Close Tab"
// "name": "Close Pane"
// "name": "[-] Split Horizontal"
// "name": "[ | ] Split Vertical"
// "name": "Next Tab"
// "name": "Prev Tab"
// "name": "Open Settings"
// "name": "Open Media Controls"
// * "open" should return both "**Open** Settings" and "**Open** Media Controls".
// * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev
// **Tab**".
// * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ]
// S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media
// Controls".
// * "sv" would return "[ | ] Split Vertical" (by matching the **S** in
// "Split", then the **V** in "Vertical").
// Arguments:
// - searchText: the string of text to search for in `name`
// - name: the name to check
// Return Value:
// - the relative weight of this match
int CommandPalette::_getWeight(const winrt::hstring& searchText,
const winrt::hstring& name)
{
int totalWeight = 0;
bool lastWasSpace = true;
auto it = name.cbegin();
for (auto searchChar : searchText)
{
searchChar = std::towlower(searchChar);
// Advance the iterator to the next character that we're looking
// for.
bool lastWasMatch = true;
while (true)
{
// If we are at the end of the name string, we haven't found
// it.
if (it == name.cend())
{
return false;
}
// found it
if (std::towlower(*it) == searchChar)
{
break;
}
lastWasSpace = *it == L' ';
++it;
lastWasMatch = false;
}
// Advance the iterator by one character so that we don't
// end up on the same character in the next iteration.
++it;
totalWeight += 1;
totalWeight += lastWasSpace ? 1 : 0;
totalWeight += (lastWasMatch) ? 1 : 0;
}
return totalWeight;
}
void CommandPalette::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
{
_dispatch = dispatch;
}
// Method Description:
// - Dismiss the command palette. This will:
// * select all the current text in the input box
// * set our visibility to Hidden
// * raise our Closed event, so the page can return focus to the active Terminal
// Arguments:
// - <none>
// Return Value:
// - <none>
void CommandPalette::_close()
{
Visibility(Visibility::Collapsed);
// Clear the text box each time we close the dialog. This is consistent with VsCode.
_searchBox().Text(L"");
}
void CommandPalette::_checkMode()
{
_mode = PaletteMode::ActionMode;
auto inputText = _searchBox().Text();
if (inputText.size() > 0)
{
if (inputText[0] == L'>')
{
_mode = PaletteMode::CommandlineMode;
}
}
}
void CommandPalette::_dispatchAction()
{
// Action Mode: Dispatch the action of the selected command.
if (const auto selectedItem = _filteredActionsView().SelectedItem())
{
if (const auto data = selectedItem.try_as<Command>())
{
const auto actionAndArgs = data.Action();
_dispatch.DoAction(actionAndArgs);
_close();
}
}
}
void CommandPalette::_dispatchCommandline()
{
const auto inputText = _searchBox().Text();
const std::wstring input{ _searchBox().Text() };
const auto rawCmdline{ input.substr(1) };
// Trim leading whitespace
const auto firstNonSpace = rawCmdline.find_first_not_of(L" ");
if (firstNonSpace == std::wstring::npos)
{
return;
}
winrt::hstring cmdline{ rawCmdline.substr(firstNonSpace) };
// Build the NewTab action from the values we've parsed on the commandline.
auto executeActionAndArgs = winrt::make_self<implementation::ActionAndArgs>();
executeActionAndArgs->Action(ShortcutAction::ExecuteCommandline);
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
args->Commandline(cmdline);
executeActionAndArgs->Args(*args);
if (_dispatch.DoAction(*executeActionAndArgs))
{
_close();
}
}
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "CommandPalette.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
namespace winrt::TerminalApp::implementation
{
enum class PaletteMode : uint32_t
{
ActionMode = 0,
CommandlineMode
};
struct CommandPalette : CommandPaletteT<CommandPalette>
{
CommandPalette();
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> FilteredActions();
void SetActions(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
private:
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allActions{ nullptr };
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
PaletteMode _mode{ PaletteMode::ActionMode };
void _filterTextChanged(Windows::Foundation::IInspectable const& sender,
Windows::UI::Xaml::RoutedEventArgs const& args);
void _keyDownHandler(Windows::Foundation::IInspectable const& sender,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _listItemClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::ItemClickEventArgs const& e);
void _selectNextItem(const bool moveDown);
void _checkMode();
void _updateFilteredActions();
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
void _close();
void _dispatchAction();
void _dispatchCommandline();
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(CommandPalette);
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "../Command.idl";
namespace TerminalApp
{
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.Grid
{
CommandPalette();
Windows.Foundation.Collections.IObservableVector<Command> FilteredActions { get; };
void SetActions(Windows.Foundation.Collections.IVector<Command> actions);
void SetDispatch(ShortcutActionDispatch dispatch);
}
}

View File

@@ -0,0 +1,204 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information. -->
<Grid
x:Class="TerminalApp.CommandPalette"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TerminalApp"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)"
PointerPressed="_rootPointerPressed"
mc:Ignorable="d">
<Grid.Resources>
<ResourceDictionary>
<!-- ThemeShadow is only on 18362. This "Windows10version1903" bit
adds it conditionally -->
<Windows10version1903:ThemeShadow x:Name="CommandPaletteShadow" />
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
<Setter Property="Background" Value="#333333" />
</Style>
<!-- TextBox colors !-->
<SolidColorBrush x:Key="TextControlBackground" Color="#333333"/>
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#B5B5B5"/>
<SolidColorBrush x:Key="TextControlForeground" Color="#B5B5B5"/>
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#404040"/>
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#B5B5B5"/>
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#404040"/>
<SolidColorBrush x:Key="TextControlForegroundPointerOver" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#404040"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#333333"/>
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#404040"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
<Setter Property="Background" Value="#CCCCCC" />
</Style>
<!-- TextBox colors !-->
<SolidColorBrush x:Key="TextControlBackground" Color="#CCCCCC"/>
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#636363"/>
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#636363"/>
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#636363"/>
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#DADADA"/>
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#636363"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#CCCCCC"/>
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#636363"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource SystemColorWindowColor}" />
</Style>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<!-- Setting the row/col span of this shadow backdrop is a bit of a hack. In
order to receive pointer events, an element needs to be _not_ transparent.
However, we want to be able to eat all the clicks outside the immediate
bounds of the command palette, and we don't want a semi-transparent overlay
over all of the UI. Fortunately, if we make this _shadowBackdrop the size of
the entire page, then it can be mostly transparent, and cause the root grid
to receive clicks _anywhere_ in its bounds. -->
<Grid
x:Name="_shadowBackdrop"
Background="Transparent"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
Grid.RowSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
</Grid>
<Grid
x:Name="_backdrop"
Style="{ThemeResource CommandPaletteBackground}"
CornerRadius="{ThemeResource ControlCornerRadius}"
PointerPressed="_backdropPointerPressed"
Margin="8"
Grid.Column="1"
Grid.Row="0"
Windows10version1903:Shadow="{StaticResource CommandPaletteShadow}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox
Grid.Row="0"
x:Uid="CommandPalette_SearchBox"
x:Name="_searchBox"
Margin="8"
IsSpellCheckEnabled="False"
TextChanged="_filterTextChanged"
KeyDown="_keyDownHandler"
Text="">
</TextBox>
<TextBlock
Padding="16"
x:Name="_noMatchesText"
x:Uid="CommandPalette_NoMatchesText"
FontStyle="Italic"
Visibility="Collapsed"
Grid.Row="1">
</TextBlock>
<ListView
Grid.Row="2"
x:Name="_filteredActionsView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SelectionMode="Single"
CanReorderItems="False"
AllowDrop="False"
IsItemClickEnabled="True"
ItemClick="_listItemClicked"
ItemsSource="{x:Bind FilteredActions}">
<ItemsControl.ItemTemplate >
<DataTemplate x:DataType="local:Command">
<!-- This HorizontalContentAlignment="Stretch" is important
to make sure it takes the entire width of the line -->
<ListViewItem HorizontalContentAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">
<Grid HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- TODO GH#6644: Add Icon to command palette entries, in column 0 -->
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
Text="{x:Bind Name, Mode=OneWay}" />
<Border
Grid.Column="2"
BorderBrush="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Background="{ThemeResource SystemControlForegroundBaseLowBrush}"
BorderThickness="1"
CornerRadius="1"
Padding="2,0,2,0"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<TextBlock
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
FontSize="12"
Text="{x:Bind KeyChordText, Mode=OneWay}" />
</Border>
</Grid>
</ListViewItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</Grid>
</Grid>

View File

@@ -7,7 +7,6 @@
#include "../../inc/DefaultSettings.h"
#include "Utils.h"
#include "JsonUtils.h"
#include <sstream>
using namespace TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings;
@@ -17,7 +16,8 @@ using namespace winrt::Windows::UI::Xaml;
using namespace ::Microsoft::Console;
using namespace winrt::Microsoft::UI::Xaml::Controls;
static constexpr std::string_view KeybindingsKey{ "keybindings" };
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
static constexpr std::string_view BindingsKey{ "bindings" };
static constexpr std::string_view DefaultProfileKey{ "defaultProfile" };
static constexpr std::string_view AlwaysShowTabsKey{ "alwaysShowTabs" };
static constexpr std::string_view InitialRowsKey{ "initialRows" };
@@ -205,18 +205,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
_TabWidthMode = _ParseTabWidthMode(GetWstringFromJson(tabWidthMode));
}
if (auto keybindings{ json[JsonKey(KeybindingsKey)] })
{
auto warnings = _keybindings->LayerJson(keybindings);
// It's possible that the user provided keybindings have some warnings
// in them - problems that we should alert the user to, but we can
// recover from. Most of these warnings cannot be detected later in the
// Validate settings phase, so we'll collect them now. If there were any
// warnings generated from parsing these keybindings, add them to our
// list of warnings.
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
}
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
JsonUtils::GetBool(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
@@ -228,6 +216,31 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
JsonUtils::GetBool(json, DebugFeaturesKey, _DebugFeaturesEnabled);
JsonUtils::GetBool(json, EnableStartupTaskKey, _StartOnUserLogin);
// This is a helper lambda to get the keybindings and commands out of both
// and array of objects. We'll use this twice, once on the legacy
// `keybindings` key, and again on the newer `bindings` key.
auto parseBindings = [this, &json](auto jsonKey) {
if (auto bindings{ json[JsonKey(jsonKey)] })
{
auto warnings = _keybindings->LayerJson(bindings);
// It's possible that the user provided keybindings have some warnings
// in them - problems that we should alert the user to, but we can
// recover from. Most of these warnings cannot be detected later in the
// Validate settings phase, so we'll collect them now. If there were any
// warnings generated from parsing these keybindings, add them to our
// list of warnings.
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
// Now parse the array again, but this time as a list of commands.
warnings = winrt::TerminalApp::implementation::Command::LayerJson(_commands, bindings);
// It's possible that the user provided commands have some warnings
// in them, similar to the keybindings.
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
}
};
parseBindings(LegacyKeybindingsKey);
parseBindings(BindingsKey);
}
// Method Description:
@@ -365,3 +378,8 @@ std::vector<TerminalApp::SettingsLoadWarnings> GlobalAppSettings::GetKeybindings
{
return _keybindingsWarnings;
}
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GlobalAppSettings::GetCommands() const noexcept
{
return _commands;
}

View File

@@ -16,6 +16,7 @@ Author(s):
#pragma once
#include "AppKeyBindings.h"
#include "ColorScheme.h"
#include "Command.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -54,6 +55,8 @@ public:
std::vector<TerminalApp::SettingsLoadWarnings> GetKeybindingsWarnings() const;
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GetCommands() const noexcept;
// These are implemented manually to handle the string/GUID exchange
// by higher layers in the app.
void DefaultProfile(const GUID defaultProfile) noexcept;
@@ -79,7 +82,6 @@ public:
GETSET_PROPERTY(bool, SoftwareRendering, false);
GETSET_PROPERTY(bool, ForceVTInput, false);
GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor
GETSET_PROPERTY(bool, StartOnUserLogin, false);
private:
@@ -90,6 +92,7 @@ private:
std::vector<::TerminalApp::SettingsLoadWarnings> _keybindingsWarnings;
std::unordered_map<std::wstring, ColorScheme> _colorSchemes;
std::unordered_map<winrt::hstring, winrt::TerminalApp::Command> _commands;
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;

View File

@@ -0,0 +1,471 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- JsonUtils.h
Abstract:
- Helpers for the TerminalApp project
Author(s):
- Mike Griese - August 2019
- Dustin Howett - January 2020
--*/
#pragma once
#include <json.h>
#include "../types/inc/utils.hpp"
namespace winrt
{
// If we don't use winrt, nobody will include the ConversionTrait for winrt::guid.
// If nobody includes it, this forward declaration will suffice.
struct guid;
}
namespace TerminalApp::JsonUtils
{
namespace Detail
{
// Function Description:
// - Returns a string_view to a Json::Value's internal string storage,
// hopefully without copying it.
__declspec(noinline) inline const std::string_view GetStringView(const Json::Value& json)
{
const char* begin{ nullptr };
const char* end{ nullptr };
json.getString(&begin, &end);
const std::string_view zeroCopyString{ begin, gsl::narrow_cast<size_t>(end - begin) };
return zeroCopyString;
}
template<typename T>
struct DeduceOptional
{
using Type = typename std::decay<T>::type;
};
template<typename TOpt>
struct DeduceOptional<std::optional<TOpt>>
{
using Type = typename std::decay<TOpt>::type;
};
}
// These exceptions cannot use localized messages, as we do not have
// guaranteed access to the resource loader.
class TypeMismatchException : public std::runtime_error
{
public:
TypeMismatchException() :
runtime_error("unexpected data type") {}
};
class KeyedException : public std::runtime_error
{
public:
KeyedException(const std::string_view key, std::exception_ptr exception) :
runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()),
_key{ key },
_innerException{ std::move(exception) } {}
std::string GetKey() const
{
return _key;
}
[[noreturn]] void RethrowInner() const
{
std::rethrow_exception(_innerException);
}
private:
std::string _key;
std::exception_ptr _innerException;
};
class UnexpectedValueException : public std::runtime_error
{
public:
UnexpectedValueException(const std::string_view value) :
runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()),
_value{ value } {}
std::string GetValue() const
{
return _value;
}
private:
std::string _value;
};
template<typename T>
struct ConversionTrait
{
// FromJson, CanConvert are not defined so as to cause a compile error (which forces a specialization)
};
template<>
struct ConversionTrait<std::string>
{
std::string FromJson(const Json::Value& json)
{
return json.asString();
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
template<>
struct ConversionTrait<std::wstring>
{
std::wstring FromJson(const Json::Value& json)
{
return til::u8u16(Detail::GetStringView(json));
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
template<>
struct ConversionTrait<bool>
{
bool FromJson(const Json::Value& json)
{
return json.asBool();
}
bool CanConvert(const Json::Value& json)
{
return json.isBool();
}
};
template<>
struct ConversionTrait<int>
{
int FromJson(const Json::Value& json)
{
return json.asInt();
}
bool CanConvert(const Json::Value& json)
{
return json.isInt();
}
};
template<>
struct ConversionTrait<unsigned int>
{
unsigned int FromJson(const Json::Value& json)
{
return json.asUInt();
}
bool CanConvert(const Json::Value& json)
{
return json.isUInt();
}
};
template<>
struct ConversionTrait<float>
{
float FromJson(const Json::Value& json)
{
return json.asFloat();
}
bool CanConvert(const Json::Value& json)
{
return json.isNumeric();
}
};
template<>
struct ConversionTrait<double>
{
double FromJson(const Json::Value& json)
{
return json.asDouble();
}
bool CanConvert(const Json::Value& json)
{
return json.isNumeric();
}
};
template<>
struct ConversionTrait<GUID>
{
GUID FromJson(const Json::Value& json)
{
return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)));
}
bool CanConvert(const Json::Value& json)
{
if (!json.isString())
{
return false;
}
const auto string{ Detail::GetStringView(json) };
return string.length() == 38 && string.front() == '{' && string.back() == '}';
}
};
// (GUID and winrt::guid are mutually convertible!)
template<>
struct ConversionTrait<winrt::guid> : public ConversionTrait<GUID>
{
};
template<>
struct ConversionTrait<til::color>
{
til::color FromJson(const Json::Value& json)
{
return ::Microsoft::Console::Utils::ColorFromHexString(Detail::GetStringView(json));
}
bool CanConvert(const Json::Value& json)
{
if (!json.isString())
{
return false;
}
const auto string{ Detail::GetStringView(json) };
return (string.length() == 7 || string.length() == 3) && string.front() == '#';
}
};
template<typename T, typename TBase>
struct EnumMapper
{
using pair_type = std::pair<std::string_view, T>;
T FromJson(const Json::Value& json)
{
const auto name{ Detail::GetStringView(json) };
for (const auto& pair : TBase::mappings)
{
if (pair.first == name)
{
return pair.second;
}
}
throw UnexpectedValueException{ name };
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
// FlagMapper is EnumMapper, but it works for bitfields.
// It supports a string (single flag) or an array of strings.
// Does an O(n*m) search; meant for small search spaces!
//
// Cleverly leverage EnumMapper to do the heavy lifting.
template<typename T, typename TBase>
struct FlagMapper : public EnumMapper<T, TBase>
{
static constexpr T AllSet{ static_cast<T>(~0u) };
static constexpr T AllClear{ static_cast<T>(0u) };
T FromJson(const Json::Value& json)
{
if (json.isString())
{
return EnumMapper::FromJson(json);
}
else if (json.isArray())
{
unsigned int seen{ 0 };
T value{};
for (const auto& element : json)
{
const auto newFlag{ EnumMapper::FromJson(element) };
if (++seen > 1 &&
((newFlag == AllClear && value != AllClear) ||
(value == AllClear && newFlag != AllClear)))
{
// attempt to combine AllClear (explicitly) with anything else
throw UnexpectedValueException{ element.asString() };
}
value |= newFlag;
}
return value;
}
// We'll only get here if CanConvert has failed us.
return AllClear;
}
bool CanConvert(const Json::Value& json)
{
return EnumMapper::CanConvert(json) || json.isArray();
}
};
// Method Description:
// - Helper that will populate a reference with a value converted from a json object.
// Arguments:
// - json: the json object to convert
// - target: the value to populate with the converted result
// Return Value:
// - a boolean indicating whether the value existed (in this case, was non-null)
//
// GetValue, type-deduced, manual converter
template<typename T, typename Converter>
bool GetValue(const Json::Value& json, T& target, Converter&& conv)
{
if (json)
{
if (!conv.CanConvert(json))
{
throw TypeMismatchException{};
}
target = conv.FromJson(json);
return true;
}
return false;
}
// Method Description:
// - Overload on GetValue that will populate a std::optional with a value converted from json
// - If the json value doesn't exist we'll leave the target object unmodified.
// - If the json object is set to `null`, then
// we'll instead set the target back to nullopt.
// Arguments:
// - json: the json object to convert
// - target: the value to populate with the converted result
// Return Value:
// - a boolean indicating whether the optional was changed
//
// GetValue, type-deduced for optional, manual converter
template<typename TOpt, typename Converter>
bool GetValue(const Json::Value& json, std::optional<TOpt>& target, Converter&& conv)
{
if (json.isNull())
{
target = std::nullopt;
return true; // null is valid for optionals
}
std::decay_t<TOpt> local{};
if (GetValue(json, local, std::forward<Converter>(conv)))
{
target = std::move(local);
return true;
}
return false;
}
// GetValue, forced return type, manual converter
template<typename T, typename Converter>
std::decay_t<T> GetValue(const Json::Value& json, Converter&& conv)
{
std::decay_t<T> local{};
GetValue(json, local, std::forward<Converter>(conv));
return local; // returns zero-initialized or value
}
// GetValueForKey, type-deduced, manual converter
template<typename T, typename Converter>
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv)
{
if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) })
{
try
{
return GetValue(*found, target, std::forward<Converter>(conv));
}
catch (...)
{
// Wrap any caught exceptions in one that preserves context.
throw KeyedException(key, std::current_exception());
}
}
return false;
}
// GetValueForKey, forced return type, manual converter
template<typename T, typename Converter>
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv)
{
std::decay_t<T> local{};
GetValueForKey(json, key, local, std::forward<Converter>(conv));
return local; // returns zero-initialized?
}
// GetValue, type-deduced, with automatic converter
template<typename T>
bool GetValue(const Json::Value& json, T& target)
{
return GetValue(json, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// GetValue, forced return type, with automatic converter
template<typename T>
std::decay_t<T> GetValue(const Json::Value& json)
{
std::decay_t<T> local{};
GetValue(json, local, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
return local; // returns zero-initialized or value
}
// GetValueForKey, type-deduced, with automatic converter
template<typename T>
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target)
{
return GetValueForKey(json, key, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// GetValueForKey, forced return type, with automatic converter
template<typename T>
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key)
{
return GetValueForKey<T>(json, key, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// Get multiple values for keys (json, k, &v, k, &v, k, &v, ...).
// Uses the default converter for each v.
// Careful: this can cause a template explosion.
constexpr void GetValuesForKeys(const Json::Value& /*json*/) {}
template<typename T, typename... Args>
void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args)
{
GetValueForKey(json, key1, val1);
GetValuesForKeys(json, std::forward<Args>(args)...);
}
};
#define JSON_ENUM_MAPPER(...) \
template<> \
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
public ::TerminalApp::JsonUtils::EnumMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
#define JSON_FLAG_MAPPER(...) \
template<> \
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
public ::TerminalApp::JsonUtils::FlagMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
#define JSON_MAPPINGS(Count) \
static constexpr std::array<pair_type, Count> mappings

View File

@@ -7,6 +7,7 @@
#include "CascadiaSettings.h"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
@@ -921,6 +922,102 @@ bool Pane::CanSplit(SplitState splitType)
return false;
}
// Method Description:
// - This is a helper to determine if a given Pane can be split, but without
// using the ActualWidth() and ActualHeight() methods. This is used during
// processing of many "split-pane" commands, which could happen _before_ we've
// laid out a Pane for the first time. When this happens, the Pane's don't
// have an actual size yet. However, we'd still like to figure out if the pane
// could be split, once they're all laid out.
// - This method assumes that the Pane we're attempting to split is `target`,
// and this method should be called on the root of a tree of Panes.
// - We'll walk down the tree attempting to find `target`. As we traverse the
// tree, we'll reduce the size passed to each subsequent recursive call. The
// size passed to this method represents how much space this Pane _will_ have
// to use.
// * If this pane is a leaf, and it's the pane we're looking for, use the
// available space to calculate which direction to split in.
// * If this pane is _any other leaf_, then just return nullopt, to indicate
// that the `target` Pane is not down this branch.
// * If this pane is a parent, calculate how much space our children will be
// able to use, and recurse into them.
// Arguments:
// - target: The Pane we're attempting to split.
// - splitType: The direction we're attempting to split in.
// - availableSpace: The theoretical space that's available for this pane to be able to split.
// Return Value:
// - nullopt if `target` is not this pane or a child of this pane, otherwise
// true iff we could split this pane, given `availableSpace`
// Note:
// - This method is highly similar to Pane::PreCalculateAutoSplit
std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> target,
SplitState splitType,
const winrt::Windows::Foundation::Size availableSpace) const
{
if (_IsLeaf())
{
if (target.get() == this)
{
//If this pane is a leaf, and it's the pane we're looking for, use
//the available space to calculate which direction to split in.
const Size minSize = _GetMinSize();
if (splitType == SplitState::None)
{
return { false };
}
if (splitType == SplitState::Vertical)
{
const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize;
const auto newWidth = widthMinusSeparator * Half;
return { newWidth > minSize.Width };
}
if (splitType == SplitState::Horizontal)
{
const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize;
const auto newHeight = heightMinusSeparator * Half;
return { newHeight > minSize.Height };
}
}
else
{
// If this pane is _any other leaf_, then just return nullopt, to
// indicate that the `target` Pane is not down this branch.
return std::nullopt;
}
}
else
{
// If this pane is a parent, calculate how much space our children will
// be able to use, and recurse into them.
const bool isVerticalSplit = _splitState == SplitState::Vertical;
const float firstWidth = isVerticalSplit ?
(availableSpace.Width * _desiredSplitPosition) - PaneBorderSize :
availableSpace.Width;
const float secondWidth = isVerticalSplit ?
(availableSpace.Width - firstWidth) - PaneBorderSize :
availableSpace.Width;
const float firstHeight = !isVerticalSplit ?
(availableSpace.Height * _desiredSplitPosition) - PaneBorderSize :
availableSpace.Height;
const float secondHeight = !isVerticalSplit ?
(availableSpace.Height - firstHeight) - PaneBorderSize :
availableSpace.Height;
const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, { firstWidth, firstHeight });
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, { secondWidth, secondHeight });
}
// We should not possibly be getting here - both the above branches should
// return a value.
FAIL_FAST();
}
// Method Description:
// - Split the focused pane in our tree of panes, and place the given
// TermControl into the newly created pane. If we're the focused pane, then

View File

@@ -64,7 +64,9 @@ public:
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
std::optional<winrt::TerminalApp::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
std::optional<bool> PreCalculateCanSplit(const std::shared_ptr<Pane> target,
winrt::TerminalApp::SplitState splitType,
const winrt::Windows::Foundation::Size availableSpace) const;
void Shutdown();
void Close();

View File

@@ -347,4 +347,158 @@
<value>Command Prompt</value>
<comment>This is the name of "Command Prompt", as localized in Windows. The localization here should match the one in the Windows product for "Command Prompt"</comment>
</data>
<data name="CommandPalette_SearchBox.PlaceholderText" xml:space="preserve">
<value>Type a command name...</value>
</data>
<data name="CommandPalette_NoMatchesText.Text" xml:space="preserve">
<value>No matching commands</value>
</data>
<data name="CloseWindowCommandKey" xml:space="preserve">
<value>Close window</value>
</data>
<data name="ToggleFullscreenCommandKey" xml:space="preserve">
<value>Toggle fullscreen</value>
</data>
<data name="OpenNewTabDropdownCommandKey" xml:space="preserve">
<value>Open new tab dropdown</value>
</data>
<data name="OpenSettingsCommandKey" xml:space="preserve">
<value>Open settings file</value>
</data>
<data name="OpenDefaultSettingsCommandKey" xml:space="preserve">
<value>Open default settings file</value>
</data>
<data name="OpenBothSettingsFilesCommandKey" xml:space="preserve">
<value>Open both settings and default settings files</value>
</data>
<data name="FindCommandKey" xml:space="preserve">
<value>Find</value>
</data>
<data name="ResizePaneCommandKey" xml:space="preserve">
<value>Resize pane</value>
</data>
<data name="MoveFocusCommandKey" xml:space="preserve">
<value>Move focus</value>
</data>
<data name="MoveFocusWithArgCommandKey" xml:space="preserve">
<value>Move focus {0}</value>
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
</data>
<data name="ResizePaneWithArgCommandKey" xml:space="preserve">
<value>Resize pane {0}</value>
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
</data>
<data name="DirectionLeft" xml:space="preserve">
<value>left</value>
</data>
<data name="DirectionRight" xml:space="preserve">
<value>right</value>
</data>
<data name="DirectionUp" xml:space="preserve">
<value>up</value>
</data>
<data name="DirectionDown" xml:space="preserve">
<value>down</value>
</data>
<data name="SwitchToTabCommandKey" xml:space="preserve">
<value>Switch to tab</value>
</data>
<data name="NewTabCommandKey" xml:space="preserve">
<value>New tab</value>
</data>
<data name="SplitPaneCommandKey" xml:space="preserve">
<value>Split pane</value>
</data>
<data name="NewWindowCommandKey" xml:space="preserve">
<value>New window</value>
</data>
<data name="DuplicateTabCommandKey" xml:space="preserve">
<value>Duplicate tab</value>
</data>
<data name="DuplicatePaneCommandKey" xml:space="preserve">
<value>Duplicate pane</value>
</data>
<data name="NextTabCommandKey" xml:space="preserve">
<value>Next tab</value>
</data>
<data name="PrevTabCommandKey" xml:space="preserve">
<value>Previous tab</value>
</data>
<data name="ClosePaneCommandKey" xml:space="preserve">
<value>Close pane</value>
</data>
<data name="CloseTabCommandKey" xml:space="preserve">
<value>Close tab</value>
</data>
<data name="SplitHorizontalCommandKey" xml:space="preserve">
<value>Split pane horizontally</value>
</data>
<data name="SplitVerticalCommandKey" xml:space="preserve">
<value>Split pane vertically</value>
</data>
<data name="CopyTextCommandKey" xml:space="preserve">
<value>Copy text</value>
</data>
<data name="CopyTextAsSingleLineCommandKey" xml:space="preserve">
<value>Copy text as a single line</value>
</data>
<data name="PasteTextCommandKey" xml:space="preserve">
<value>Paste</value>
</data>
<data name="ScrollDownCommandKey" xml:space="preserve">
<value>Scroll down one line</value>
</data>
<data name="ScrollDownPageCommandKey" xml:space="preserve">
<value>Scroll down one page</value>
</data>
<data name="ScrollUpCommandKey" xml:space="preserve">
<value>Scroll up one line</value>
</data>
<data name="ScrollUpPageCommandKey" xml:space="preserve">
<value>Scroll up one page</value>
</data>
<data name="AdjustFontSizeCommandKey" xml:space="preserve">
<value>Adjust font size</value>
</data>
<data name="IncreaseFontSizeCommandKey" xml:space="preserve">
<value>Increase font size</value>
</data>
<data name="DecreaseFontSizeCommandKey" xml:space="preserve">
<value>Decrease font size</value>
</data>
<data name="IncreaseFontSizeWithAmountCommandKey" xml:space="preserve">
<value>Increase font size, amount: {0}</value>
<comment>{0} will be replaced with a positive number</comment>
</data>
<data name="DecreaseFontSizeWithAmountCommandKey" xml:space="preserve">
<value>Decrease font size, amount: {0}</value>
<comment>{0} will be replaced with a positive number</comment>
</data>
<data name="ResetFontSizeCommandKey" xml:space="preserve">
<value>Reset font size</value>
</data>
<data name="ToggleCommandPaletteCommandKey" xml:space="preserve">
<value>Toggle command palette</value>
</data>
<data name="ExecuteCommandlineCommandKey" xml:space="preserve">
<value>Run the commandline "{0}" in this window</value>
<comment>{0} will be replaced with a user-provided commandline</comment>
</data>
<data name="SetTabColorCommandKey" xml:space="preserve">
<value>Set tab color to {0}</value>
<comment>{0} will be replaced with a color, displayed in hexadecimal (#RRGGBB) notation.</comment>
</data>
<data name="ResetTabColorCommandKey" xml:space="preserve">
<value>Reset tab color</value>
</data>
<data name="OpenTabColorPickerCommandKey" xml:space="preserve">
<value>Set the tab color...</value>
</data>
<data name="RenameTabCommandKey" xml:space="preserve">
<value>Rename tab to "{0}"</value>
<comment>{0} will be replaced with user-defined string</comment>
</data>
<data name="ResetTabNameCommandKey" xml:space="preserve">
<value>Reset tab title</value>
</data>
</root>

View File

@@ -159,6 +159,30 @@ namespace winrt::TerminalApp::implementation
_ToggleFullscreenHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::ToggleCommandPalette:
{
_ToggleCommandPaletteHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::ExecuteCommandline:
{
_ExecuteCommandlineHandlers(*this, *eventArgs);
}
case ShortcutAction::SetTabColor:
{
_SetTabColorHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::OpenTabColorPicker:
{
_OpenTabColorPickerHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::RenameTab:
{
_RenameTabHandlers(*this, *eventArgs);
break;
}
default:
return false;
}

View File

@@ -23,30 +23,35 @@ namespace winrt::TerminalApp::implementation
bool DoAction(const ActionAndArgs& actionAndArgs);
// clang-format off
TYPED_EVENT(CopyText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(PasteText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(OpenNewTabDropdown,TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(DuplicateTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(CloseWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(CloseTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ClosePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollDown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollUpPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollDownPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(CopyText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(PasteText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(OpenNewTabDropdown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(DuplicateTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(CloseWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(CloseTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ClosePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollDown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollUpPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ScrollDownPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ToggleCommandPalette, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(ExecuteCommandline, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(SetTabColor, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(OpenTabColorPicker, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
TYPED_EVENT(RenameTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
// clang-format on
private:

View File

@@ -4,9 +4,6 @@ import "../ActionArgs.idl";
namespace TerminalApp
{
// TODO: GH#1069 - Many of these shortcut actions are "legacy" now that we
// have support for arbitrary args (#1142). We should remove them, and our
// legacy deserializers.
enum ShortcutAction
{
Invalid = 0,
@@ -35,7 +32,12 @@ namespace TerminalApp
MoveFocus,
Find,
ToggleFullscreen,
OpenSettings
SetTabColor,
OpenTabColorPicker,
OpenSettings,
ExecuteCommandline,
RenameTab,
ToggleCommandPalette
};
[default_interface] runtimeclass ActionAndArgs {
@@ -73,5 +75,10 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> Find;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> MoveFocus;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleFullscreen;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleCommandPalette;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ExecuteCommandline;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> SetTabColor;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> OpenTabColorPicker;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> RenameTab;
}
}

View File

@@ -45,6 +45,15 @@ namespace winrt::TerminalApp::implementation
void Tab::_MakeTabViewItem()
{
_tabViewItem = ::winrt::MUX::Controls::TabViewItem{};
_tabViewItem.DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) {
if (auto tab{ weakThis.get() })
{
tab->_inRename = true;
tab->_UpdateTabHeader();
}
});
_UpdateTitle();
}
@@ -378,6 +387,18 @@ namespace winrt::TerminalApp::implementation
_activePane->Close();
}
void Tab::SetTabText(winrt::hstring title)
{
_runtimeTabText = title;
_UpdateTitle();
}
void Tab::ResetTabText()
{
_runtimeTabText = L"";
_UpdateTitle();
}
// Method Description:
// - Register any event handlers that we may need with the given TermControl.
// This should be called on each and every TermControl that we add to the tree
@@ -499,7 +520,7 @@ namespace winrt::TerminalApp::implementation
chooseColorMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_tabColorPickup.ShowAt(tab->_tabViewItem);
tab->ActivateColorPicker();
}
});
chooseColorMenuItem.Text(RS_(L"TabColorChoose"));
@@ -509,14 +530,14 @@ namespace winrt::TerminalApp::implementation
_tabColorPickup.ColorSelected([weakThis](auto newTabColor) {
if (auto tab{ weakThis.get() })
{
tab->_SetTabColor(newTabColor);
tab->SetTabColor(newTabColor);
}
});
_tabColorPickup.ColorCleared([weakThis]() {
if (auto tab{ weakThis.get() })
{
tab->_ResetTabColor();
tab->ResetTabColor();
}
});
@@ -697,7 +718,7 @@ namespace winrt::TerminalApp::implementation
// - color: the shiny color the user picked for their tab
// Return Value:
// - <none>
void Tab::_SetTabColor(const winrt::Windows::UI::Color& color)
void Tab::SetTabColor(const winrt::Windows::UI::Color& color)
{
auto weakThis{ get_weak() };
@@ -758,7 +779,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_ResetTabColor()
void Tab::ResetTabColor()
{
auto weakThis{ get_weak() };
@@ -796,6 +817,17 @@ namespace winrt::TerminalApp::implementation
});
}
// Method Description:
// - Display the tab color picker at the location of the TabViewItem for this tab.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::ActivateColorPicker()
{
_tabColorPickup.ShowAt(_tabViewItem);
}
// Method Description:
// Toggles the visual state of the tab view item,
// so that changes to the tab color are reflected immediately
@@ -843,6 +875,10 @@ namespace winrt::TerminalApp::implementation
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
}
bool Tab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const
{
return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false);
}
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>);

View File

@@ -40,6 +40,7 @@ namespace winrt::TerminalApp::implementation
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
bool PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const;
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::TerminalApp::Direction& direction);
@@ -51,8 +52,15 @@ namespace winrt::TerminalApp::implementation
void Shutdown();
void ClosePane();
void SetTabText(winrt::hstring title);
void ResetTabText();
std::optional<winrt::Windows::UI::Color> GetTabColor();
void SetTabColor(const winrt::Windows::UI::Color& color);
void ResetTabColor();
void ActivateColorPicker();
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
@@ -80,8 +88,6 @@ namespace winrt::TerminalApp::implementation
void _Focus();
void _CreateContextMenu();
void _SetTabColor(const winrt::Windows::UI::Color& color);
void _ResetTabColor();
void _RefreshVisualState();
void _BindEventHandlers(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) noexcept;

View File

@@ -14,6 +14,7 @@
#include <winrt/Windows.Storage.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
#include "KeyChordSerialization.h"
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
#include "TelnetGenerator.h" // For TelnetConnectionType
#include "TabRowControl.h"
@@ -49,13 +50,39 @@ namespace winrt::TerminalApp::implementation
InitializeComponent();
}
void TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI)
winrt::fire_and_forget TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings,
bool needRefreshUI)
{
_settings = settings;
if (needRefreshUI)
{
_RefreshUIForSettingsReload();
}
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
if (auto page{ weakThis.get() })
{
// Update the command palette when settings reload
auto commandsCollection = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
for (auto& nameAndCommand : _settings->GlobalSettings().GetCommands())
{
auto command = nameAndCommand.second;
// If there's a keybinding that's bound to exactly this command,
// then get the string for that keychord and display it as a
// part of the command in the UI. Each Command's KeyChordText is
// unset by default, so we don't need to worry about clearing it
// if there isn't a key associated with it.
auto keyChord{ _settings->GetKeybindings().GetKeyBindingForActionWithArgs(command.Action()) };
if (keyChord)
{
command.KeyChordText(KeyChordSerialization::ToString(keyChord));
}
commandsCollection.Append(command);
}
CommandPalette().SetActions(commandsCollection);
}
}
void TerminalPage::Create()
@@ -151,6 +178,17 @@ namespace winrt::TerminalApp::implementation
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
CommandPalette().SetDispatch(*_actionDispatch);
// When the visibility of the command palette changes to "collapsed",
// the palette has been closed. Toss focus back to the currently active
// control.
CommandPalette().RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
if (CommandPalette().Visibility() == Visibility::Collapsed)
{
_CommandPaletteClosed(nullptr, nullptr);
}
});
// Once the page is actually laid out on the screen, trigger all our
// startup actions. Things like Panes need to know at least how big the
// window will be, so they can subdivide that space.
@@ -192,7 +230,7 @@ namespace winrt::TerminalApp::implementation
}
else
{
_ProcessStartupActions();
_ProcessStartupActions(true);
}
}
}
@@ -201,10 +239,11 @@ namespace winrt::TerminalApp::implementation
// - Process all the startup actions in our list of startup actions. We'll
// do this all at once here.
// Arguments:
// - <none>
// - initial: if true, we're parsing these args during startup, and we
// should fire an Initialized event.
// Return Value:
// - <none>
winrt::fire_and_forget TerminalPage::_ProcessStartupActions()
winrt::fire_and_forget TerminalPage::_ProcessStartupActions(const bool initial)
{
// If there are no actions left, do nothing.
if (_startupActions.empty())
@@ -214,15 +253,24 @@ namespace winrt::TerminalApp::implementation
auto weakThis{ get_weak() };
// Handle it on a subsequent pass of the UI thread.
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
if (auto page{ weakThis.get() })
for (const auto& action : _startupActions)
{
for (const auto& action : _startupActions)
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
if (auto page{ weakThis.get() })
{
_actionDispatch->DoAction(action);
}
_CompleteInitialization();
else
{
return;
}
}
if (auto page{ weakThis.get() })
{
if (initial)
{
_CompleteInitialization();
}
}
}
@@ -755,6 +803,11 @@ namespace winrt::TerminalApp::implementation
_actionDispatch->Find({ this, &TerminalPage::_HandleFind });
_actionDispatch->ResetFontSize({ this, &TerminalPage::_HandleResetFontSize });
_actionDispatch->ToggleFullscreen({ this, &TerminalPage::_HandleToggleFullscreen });
_actionDispatch->ToggleCommandPalette({ this, &TerminalPage::_HandleToggleCommandPalette });
_actionDispatch->ExecuteCommandline({ this, &TerminalPage::_HandleExecuteCommandline });
_actionDispatch->SetTabColor({ this, &TerminalPage::_HandleSetTabColor });
_actionDispatch->OpenTabColorPicker({ this, &TerminalPage::_HandleOpenTabColorPicker });
_actionDispatch->RenameTab({ this, &TerminalPage::_HandleRenameTab });
}
// Method Description:
@@ -1096,6 +1149,18 @@ namespace winrt::TerminalApp::implementation
return std::nullopt;
}
// Method Description:
// - returns a com_ptr to the currently focused tab. This might return null,
// so make sure to check the result!
winrt::com_ptr<Tab> TerminalPage::_GetFocusedTab()
{
if (auto index{ _GetFocusedTabIndex() })
{
return _GetStrongTabImpl(*index);
}
return nullptr;
}
// Method Description:
// - An async method for changing the focused tab on the UI thread. This
// method will _only_ set the selected item of the TabView, which will
@@ -1250,19 +1315,20 @@ namespace winrt::TerminalApp::implementation
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
const auto canSplit = focusedTab->CanSplitPane(splitType);
if (!canSplit && _startupState == StartupState::Initialized)
{
return;
}
float contentWidth = gsl::narrow_cast<float>(_tabContent.ActualWidth());
float contentHeight = gsl::narrow_cast<float>(_tabContent.ActualHeight());
winrt::Windows::Foundation::Size availableSpace{ contentWidth, contentHeight };
auto realSplitType = splitType;
if (realSplitType == SplitState::Automatic && _startupState < StartupState::Initialized)
if (realSplitType == SplitState::Automatic)
{
float contentWidth = gsl::narrow_cast<float>(_tabContent.ActualWidth());
float contentHeight = gsl::narrow_cast<float>(_tabContent.ActualHeight());
realSplitType = focusedTab->PreCalculateAutoSplit({ contentWidth, contentHeight });
realSplitType = focusedTab->PreCalculateAutoSplit(availableSpace);
}
const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, availableSpace);
if (!canSplit)
{
return;
}
TermControl newControl{ controlSettings, controlConnection };
@@ -2016,6 +2082,16 @@ namespace winrt::TerminalApp::implementation
// TODO GH#3327: Look at what to do with the NC area when we have XAML theming
}
void TerminalPage::_CommandPaletteClosed(const IInspectable& /*sender*/,
const RoutedEventArgs& /*eventArgs*/)
{
// Return focus to the active control
if (auto index{ _GetFocusedTabIndex() })
{
_GetStrongTabImpl(index.value())->SetFocused(true);
}
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View File

@@ -32,7 +32,7 @@ namespace winrt::TerminalApp::implementation
public:
TerminalPage();
void SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI);
winrt::fire_and_forget SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI);
void Create();
@@ -93,7 +93,7 @@ namespace winrt::TerminalApp::implementation
StartupState _startupState{ StartupState::NotInitialized };
std::deque<winrt::TerminalApp::ActionAndArgs> _startupActions;
winrt::fire_and_forget _ProcessStartupActions();
winrt::fire_and_forget _ProcessStartupActions(const bool initial);
void _ShowAboutDialog();
void _ShowCloseWarningDialog();
@@ -129,6 +129,7 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
winrt::com_ptr<Tab> _GetFocusedTab();
winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex);
void _CloseFocusedTab();
void _CloseFocusedPane();
@@ -172,6 +173,8 @@ namespace winrt::TerminalApp::implementation
void _CompleteInitialization();
void _CommandPaletteClosed(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
@@ -197,6 +200,12 @@ namespace winrt::TerminalApp::implementation
void _HandleFind(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleResetFontSize(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleToggleFullscreen(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleSetTabColor(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleOpenTabColorPicker(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleRenameTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleToggleCommandPalette(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleExecuteCommandline(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
// Make sure to hook new actions up in _RegisterActionCallbacks!
#pragma endregion
friend class TerminalAppLocalTests::TabTests;

View File

@@ -53,5 +53,11 @@ the MIT License. See LICENSE in the project root for license information. -->
DefaultButton="Primary"
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick">
</ContentDialog>
<local:CommandPalette
x:Name="CommandPalette"
Grid.Row="1"
Visibility="Collapsed"
VerticalAlignment="Stretch" />
</Grid>
</Page>

View File

@@ -76,7 +76,7 @@ std::vector<TerminalApp::Profile> WslDistroGenerator::GenerateProfiles()
THROW_HR(ERROR_UNHANDLED_EXCEPTION);
}
DWORD exitCode;
if (GetExitCodeProcess(pi.hProcess, &exitCode) == false)
if (!GetExitCodeProcess(pi.hProcess, &exitCode))
{
THROW_HR(E_INVALIDARG);
}
@@ -116,7 +116,7 @@ std::vector<TerminalApp::Profile> WslDistroGenerator::GenerateProfiles()
continue;
}
size_t firstChar = distName.find_first_of(L"( ");
const size_t firstChar = distName.find_first_of(L"( ");
// Some localizations don't have a space between the name and "(Default)"
// https://github.com/microsoft/terminal/issues/1168#issuecomment-500187109
if (firstChar < distName.size())

View File

@@ -63,6 +63,9 @@
<Page Include="../ColorPickupFlyout.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="../CommandPalette.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<!-- ========================= Headers ======================== -->
<ItemGroup>
@@ -85,6 +88,12 @@
<ClInclude Include="../ColorPickupFlyout.h">
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="../CommandPalette.h">
<DependentUpon>../CommandPalette.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="../Command.h">
<DependentUpon>../Command.idl</DependentUpon>
</ClInclude>
<ClInclude Include="../Tab.h">
<DependentUpon>../Tab.idl</DependentUpon>
</ClInclude>
@@ -103,7 +112,7 @@
<ClInclude Include="../WslDistroGenerator.h" />
<ClInclude Include="../AzureCloudShellGenerator.h" />
<ClInclude Include="../TelnetGenerator.h" />
<ClInclude Include="..\ColorHelper.h" />
<ClInclude Include="../ColorHelper.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="../ShortcutActionDispatch.h">
<DependentUpon>../ShortcutActionDispatch.idl</DependentUpon>
@@ -146,6 +155,12 @@
<ClCompile Include="../ColorPickupFlyout.cpp">
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="../CommandPalette.cpp">
<DependentUpon>../CommandPalette.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="../Command.cpp">
<DependentUpon>../Command.idl</DependentUpon>
</ClCompile>
<ClCompile Include="../Tab.cpp">
<DependentUpon>../Tab.idl</DependentUpon>
</ClCompile>
@@ -229,7 +244,12 @@
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="../Tab.idl"/>
<Midl Include="../CommandPalette.idl">
<DependentUpon>../CommandPalette.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="../Command.idl" />
<Midl Include="../Tab.idl" />
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>

View File

@@ -4,7 +4,7 @@
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="../Resources/en-US/Resources.resw" />
<PRIResource Include="..\Resources\en-US\Resources.resw" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="../init.cpp" />
@@ -59,7 +59,13 @@
<ClCompile Include="../Pane.LayoutSizeNode.cpp">
<Filter>pane</Filter>
</ClCompile>
<ClCompile Include="..\ColorHelper.cpp" />
<ClCompile Include="../AppCommandlineArgs.cpp" />
<ClCompile Include="../Commandline.cpp" />
<ClCompile Include="../ColorHelper.cpp" />
<ClCompile Include="../DebugTapConnection.cpp" />
<ClCompile Include="../CommandSerialization.cpp">
<Filter>settings</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="../Utils.h" />
@@ -107,8 +113,16 @@
<ClInclude Include="../Tab.h">
<Filter>tab</Filter>
</ClInclude>
<ClInclude Include="../TelnetGenerator.h" />
<ClInclude Include="..\ColorHelper.h" />
<ClInclude Include="../AppCommandlineArgs.h" />
<ClInclude Include="../Commandline.h" />
<ClInclude Include="../DebugTapConnection.h" />
<ClInclude Include="../ColorHelper.h" />
<ClInclude Include="../TelnetGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="../CommandSerialization.h">
<Filter>settings</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Midl Include="../AppLogic.idl">
@@ -126,6 +140,10 @@
<Midl Include="../Tab.idl">
<Filter>tab</Filter>
</Midl>
<Midl Include="../IF7Listener.idl" />
<Midl Include="../Command.idl">
<Filter>commandPalette</Filter>
</Midl>
</ItemGroup>
<ItemGroup>
<None Include="../packages.config" />
@@ -146,6 +164,9 @@
<Page Include="../ColorPickupFlyout.xaml">
<Filter>controls</Filter>
</Page>
<Page Include="../CommandPalette.xaml">
<Filter>commandPalette</Filter>
</Page>
</ItemGroup>
<ItemGroup>
<Filter Include="app">
@@ -169,6 +190,9 @@
<Filter Include="tab">
<UniqueIdentifier>{6d40e12f-b83f-462e-8f93-fa421f87b27e}</UniqueIdentifier>
</Filter>
<Filter Include="commandPalette">
<UniqueIdentifier>{2ad498e1-d8ea-4381-9464-a74c141bd7dd}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="..\App.xaml">

View File

@@ -28,6 +28,8 @@
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Graphics.Display.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
#include <winrt/Windows.UI.Text.h>

View File

@@ -17,6 +17,7 @@
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Terminal::Core;
using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Input;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
@@ -31,6 +32,9 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
// The updates are throttled to limit power usage.
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the TSF input control.
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
@@ -124,31 +128,36 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
});
_tsfTryRedrawCanvas = std::make_shared<ThrottledFunc<>>(
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() })
{
control->TSFInputControl().TryRedrawCanvas();
}
},
TsfRedrawInterval,
Dispatcher());
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
[weakThis = get_weak()](const auto& update) {
if (auto control{ weakThis.get() })
{
control->Dispatcher()
.RunAsync(CoreDispatcherPriority::Normal, [=]() {
if (auto control2{ weakThis.get() })
{
control2->_isInternalScrollBarUpdate = true;
control->_isInternalScrollBarUpdate = true;
auto scrollBar = control2->ScrollBar();
if (update.newValue.has_value())
{
scrollBar.Value(update.newValue.value());
}
scrollBar.Maximum(update.newMaximum);
scrollBar.Minimum(update.newMinimum);
scrollBar.ViewportSize(update.newViewportSize);
auto scrollBar = control->ScrollBar();
if (update.newValue.has_value())
{
scrollBar.Value(update.newValue.value());
}
scrollBar.Maximum(update.newMaximum);
scrollBar.Minimum(update.newMinimum);
scrollBar.ViewportSize(update.newViewportSize);
control2->_isInternalScrollBarUpdate = false;
}
});
control->_isInternalScrollBarUpdate = false;
}
},
ScrollBarUpdateInterval);
ScrollBarUpdateInterval,
Dispatcher());
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
@@ -250,14 +259,32 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Update our control settings
_ApplyUISettings();
// Update DxEngine's SelectionBackground
_renderEngine->SetSelectionBackground(_settings.SelectionBackground());
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
auto lock = _terminal->LockForWriting();
// Update DxEngine settings under the lock
_renderEngine->SetSelectionBackground(_settings.SelectionBackground());
_renderEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
switch (_settings.AntialiasingMode())
{
case TextAntialiasingMode::Cleartype:
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
break;
case TextAntialiasingMode::Aliased:
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
break;
case TextAntialiasingMode::Grayscale:
default:
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
break;
}
// Refresh our font with the renderer
const auto actualFontOldSize = _actualFont.GetSize();
_UpdateFont();
@@ -595,7 +622,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Then, using the font, get the number of characters that can fit.
// Resize our terminal connection to match that size, and initialize the terminal with that size.
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
LOG_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
// Update DxEngine's SelectionBackground
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
@@ -611,13 +638,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_terminal->CreateFromSettings(_settings, renderTarget);
// TODO:GH#3927 - Make it possible to hot-reload these settings. Right
// here, the setting will only be used when the Terminal is initialized.
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
dxEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
dxEngine->SetSoftwareRendering(_settings.SoftwareRendering());
// TODO:GH#3927 - hot-reload this one too
// Update DxEngine's AntialiasingMode
switch (_settings.AntialiasingMode())
{
@@ -799,7 +823,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto modifiers = _GetPressedModifierKeys();
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
bool handled = false;
// Alt-Numpad# input will send us a character once the user releases
// Alt, so we should be ignoring the individual keydowns. The character
@@ -822,34 +845,43 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// keybindings on the keyUp, then we'll still send the keydown to the
// connected terminal application, and something like ctrl+shift+T will
// emit a ^T to the pipe.
if (!modifiers.IsAltGrPressed() && keyDown)
if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, modifiers))
{
auto bindings = _settings.KeyBindings();
if (bindings)
{
handled = bindings.TryKeyChord({
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
vkey,
});
}
e.Handled(true);
return;
}
if (!handled)
if (_TrySendKeyEvent(vkey, scanCode, modifiers, keyDown))
{
handled = _TrySendKeyEvent(vkey, scanCode, modifiers, keyDown);
e.Handled(true);
return;
}
// Manually prevent keyboard navigation with tab. We want to send tab to
// the terminal, and we don't want to be able to escape focus of the
// control with tab.
if (e.OriginalKey() == VirtualKey::Tab)
e.Handled(e.OriginalKey() == VirtualKey::Tab);
}
// Method Description:
// - Attempt to handle this key combination as a key binding
// Arguments:
// - vkey: The vkey of the key pressed.
// - modifiers: The ControlKeyStates representing the modifier key states.
bool TermControl::_TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const
{
auto bindings = _settings.KeyBindings();
if (!bindings)
{
handled = true;
return false;
}
e.Handled(handled);
return bindings.TryKeyChord({
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
vkey,
});
}
// Method Description:
@@ -1047,35 +1079,43 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const unsigned int MAX_CLICK_COUNT = 3;
const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount;
if (multiClickMapper == 3)
::Terminal::SelectionExpansionMode mode = ::Terminal::SelectionExpansionMode::Cell;
if (multiClickMapper == 1)
{
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Line);
_selectionNeedsToBeCopied = true;
mode = ::Terminal::SelectionExpansionMode::Cell;
}
else if (multiClickMapper == 2)
{
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Word);
mode = ::Terminal::SelectionExpansionMode::Word;
}
else if (multiClickMapper == 3)
{
mode = ::Terminal::SelectionExpansionMode::Line;
}
// Update the selection appropriately
if (shiftEnabled && _terminal->IsSelectionActive())
{
// Shift+Click: only set expand on the "end" selection point
_terminal->SetSelectionEnd(terminalPosition, mode);
_selectionNeedsToBeCopied = true;
}
else if (mode == ::Terminal::SelectionExpansionMode::Cell)
{
// Single Click: reset the selection and begin a new one
_terminal->ClearSelection();
_singleClickTouchdownPos = cursorPosition;
_selectionNeedsToBeCopied = false; // there's no selection, so there's nothing to update
}
else
{
if (shiftEnabled && _terminal->IsSelectionActive())
{
_terminal->SetSelectionEnd(terminalPosition, ::Terminal::SelectionExpansionMode::Cell);
_selectionNeedsToBeCopied = true;
}
else
{
// A single click down resets the selection and begins a new one.
_terminal->ClearSelection();
_singleClickTouchdownPos = cursorPosition;
_selectionNeedsToBeCopied = false; // there's no selection, so there's nothing to update
}
_lastMouseClickTimestamp = point.Timestamp();
_lastMouseClickPos = cursorPosition;
// Multi-Click Selection: expand both "start" and "end" selection points
_terminal->MultiClickSelection(terminalPosition, mode);
_selectionNeedsToBeCopied = true;
}
_lastMouseClickTimestamp = point.Timestamp();
_lastMouseClickPos = cursorPosition;
_renderer->TriggerSelection();
}
else if (point.Properties().IsRightButtonPressed())
@@ -2024,42 +2064,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// to be where the current cursor position is.
// Arguments:
// - N/A
winrt::fire_and_forget TermControl::_TerminalCursorPositionChanged()
void TermControl::_TerminalCursorPositionChanged()
{
bool expectedFalse{ false };
if (!_coroutineDispatchStateUpdateInProgress.compare_exchange_weak(expectedFalse, true))
{
// somebody's already in here.
return;
}
if (_closing.load())
{
return;
}
auto dispatcher{ Dispatcher() }; // cache a strong ref to this in case TermControl dies
auto weakThis{ get_weak() };
// Muffle 2: Muffle Harder
// If we're the lucky coroutine who gets through, we'll still wait 100ms to clog
// the atomic above so we don't service the cursor update too fast. If we get through
// and finish processing the update quickly but similar requests are still beating
// down the door above in the atomic, we may still update the cursor way more than
// is visible to anyone's eye, which is a waste of effort.
static constexpr auto CursorUpdateQuiesceTime{ std::chrono::milliseconds(100) };
co_await winrt::resume_after(CursorUpdateQuiesceTime);
co_await winrt::resume_foreground(dispatcher);
if (auto control{ weakThis.get() })
{
if (!_closing.load())
{
TSFInputControl().TryRedrawCanvas();
}
_coroutineDispatchStateUpdateInProgress.store(false);
}
_tsfTryRedrawCanvas->Run();
}
hstring TermControl::Title()
@@ -2236,28 +2243,68 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// as font size, scrollbar and other control scaling, etc. Make sure the
// caller knows what monitor the control is about to appear on.
// Return Value:
// - a point containing the requested dimensions in pixels.
winrt::Windows::Foundation::Point TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi)
// - a size containing the requested dimensions in pixels.
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi)
{
// If the settings have negative or zero row or column counts, ignore those counts.
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
// we may eventually impose different ones depending on how many pixels we can address.)
const auto cols = ::base::saturated_cast<float>(std::max(settings.InitialCols(), 1));
const auto rows = ::base::saturated_cast<float>(std::max(settings.InitialRows(), 1));
const winrt::Windows::Foundation::Size initialSize{ cols, rows };
return GetProposedDimensions(initialSize,
settings.FontSize(),
settings.FontWeight(),
settings.FontFace(),
settings.ScrollState(),
settings.Padding(),
dpi);
}
// Function Description:
// - Determines how much space (in pixels) an app would need to reserve to
// create a control with the settings stored in the settings param. This
// accounts for things like the font size and face, the initialRows and
// initialCols, and scrollbar visibility. The returned sized is based upon
// the provided DPI value
// Arguments:
// - initialSizeInChars: The size to get the proposed dimensions for.
// - fontHeight: The font height to use to calculate the proposed size for.
// - fontWeight: The font weight to use to calculate the proposed size for.
// - fontFace: The font name to use to calculate the proposed size for.
// - scrollState: The ScrollbarState to use to calculate the proposed size for.
// - padding: The padding to use to calculate the proposed size for.
// - dpi: The DPI we should create the terminal at. This affects things such
// as font size, scrollbar and other control scaling, etc. Make sure the
// caller knows what monitor the control is about to appear on.
// Return Value:
// - a size containing the requested dimensions in pixels.
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
const int32_t& fontHeight,
const winrt::Windows::UI::Text::FontWeight& fontWeight,
const winrt::hstring& fontFace,
const Microsoft::Terminal::Settings::ScrollbarState& scrollState,
const winrt::hstring& padding,
const uint32_t dpi)
{
const auto cols = ::base::saturated_cast<int>(initialSizeInChars.Width);
const auto rows = ::base::saturated_cast<int>(initialSizeInChars.Height);
// Initialize our font information.
const auto fontFace = settings.FontFace();
const short fontHeight = gsl::narrow_cast<short>(settings.FontSize());
const auto fontWeight = settings.FontWeight();
// const auto fontFace = settings.FontFace();
// const short fontHeight = gsl::narrow_cast<short>(fontSize);
// const auto fontWeight = settings.FontWeight();
// The font width doesn't terribly matter, we'll only be using the
// height to look it up
// The other params here also largely don't matter.
// The family is only used to determine if the font is truetype or
// not, but DX doesn't use that info at all.
// The Codepage is additionally not actually used by the DX engine at all.
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, fontHeight }, CP_UTF8, false };
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast<short>(fontHeight) }, CP_UTF8, false };
FontInfoDesired desiredFont = { actualFont };
// If the settings have negative or zero row or column counts, ignore those counts.
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
// we may eventually impose different ones depending on how many pixels we can address.)
const auto cols = std::max(settings.InitialCols(), 1);
const auto rows = std::max(settings.InitialRows(), 1);
// Create a DX engine and initialize it with our font and DPI. We'll
// then use it to measure how much space the requested rows and columns
// will take up.
@@ -2277,13 +2324,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
double width = cols * fontSize.X;
// Reserve additional space if scrollbar is intended to be visible
if (settings.ScrollState() == ScrollbarState::Visible)
if (scrollState == ScrollbarState::Visible)
{
width += scrollbarSize;
}
double height = rows * fontSize.Y;
auto thickness = _ParseThicknessFromPadding(settings.Padding());
auto thickness = _ParseThicknessFromPadding(padding);
// GH#2061 - make sure to account for the size the padding _will be_ scaled to
width += scale * (thickness.Left + thickness.Right);
height += scale * (thickness.Top + thickness.Bottom);
@@ -2316,21 +2363,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// have a visible character.
winrt::Windows::Foundation::Size TermControl::MinimumSize()
{
const auto fontSize = _actualFont.GetSize();
double width = fontSize.X;
double height = fontSize.Y;
// Reserve additional space if scrollbar is intended to be visible
if (_settings.ScrollState() == ScrollbarState::Visible)
if (_initializedTerminal)
{
width += ScrollBar().ActualWidth();
const auto fontSize = _actualFont.GetSize();
double width = fontSize.X;
double height = fontSize.Y;
// Reserve additional space if scrollbar is intended to be visible
if (_settings.ScrollState() == ScrollbarState::Visible)
{
width += ScrollBar().ActualWidth();
}
// Account for the size of any padding
const auto padding = SwapChainPanel().Margin();
width += padding.Left + padding.Right;
height += padding.Top + padding.Bottom;
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
}
else
{
// If the terminal hasn't been initialized yet, then the font size will
// have dimensions {1, fontSize.Y}, which can mess with consumers of
// this method. In that case, we'll need to pre-calculate the font
// width, before we actually have a renderer or swapchain.
const winrt::Windows::Foundation::Size minSize{ 1, 1 };
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
const auto dpi = ::base::saturated_cast<uint32_t>(USER_DEFAULT_SCREEN_DPI * scaleFactor);
return GetProposedDimensions(minSize,
_settings.FontSize(),
_settings.FontWeight(),
_settings.FontFace(),
_settings.ScrollState(),
_settings.Padding(),
dpi);
}
// Account for the size of any padding
const auto padding = SwapChainPanel().Margin();
width += padding.Left + padding.Right;
height += padding.Top + padding.Bottom;
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
}
// Method Description:

View File

@@ -98,7 +98,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
TerminalConnection::ConnectionState ConnectionState() const;
static Windows::Foundation::Point GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi);
static Windows::Foundation::Size GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi);
static Windows::Foundation::Size GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
const int32_t& fontSize,
const winrt::Windows::UI::Text::FontWeight& fontWeight,
const winrt::hstring& fontFace,
const Microsoft::Terminal::Settings::ScrollbarState& scrollState,
const winrt::hstring& padding,
const uint32_t dpi);
// clang-format off
// -------------------------------- WinRT Events ---------------------------------
@@ -136,6 +143,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
FontInfoDesired _desiredFont;
FontInfo _actualFont;
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
struct ScrollBarUpdate
{
std::optional<double> newValue;
@@ -210,7 +219,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _RefreshSizeUnderLock();
void _TerminalTitleChanged(const std::wstring_view& wstr);
void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
winrt::fire_and_forget _TerminalCursorPositionChanged();
void _TerminalCursorPositionChanged();
void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed);
void _MouseZoomHandler(const double delta);
@@ -228,6 +237,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown);
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
bool _TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
bool _CanSendVTMouseInput();
@@ -245,12 +255,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
winrt::fire_and_forget _AsyncCloseConnection();
// this atomic is to be used as a guard against dispatching billions of coroutines for
// routine state changes that might happen millions of times a second.
// Unbounded main dispatcher use leads to massive memory leaks and intense slowdowns
// on the UI thread.
std::atomic<bool> _coroutineDispatchStateUpdateInProgress{ false };
};
}

View File

@@ -35,7 +35,7 @@ namespace Microsoft.Terminal.TerminalControl
TermControl();
TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
static Windows.Foundation.Point GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi);
static Windows.Foundation.Size GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi);
void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings);

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