Compare commits

...

49 Commits

Author SHA1 Message Date
Dustin L. Howett
e2fe05c990 Migrate spelling-0.0.21 changes from main 2020-04-13 11:08:51 -05:00
Dustin L. Howett
93b1e6e242 Migrate spelling-0.0.19 changes from main 2020-04-13 11:08:51 -05:00
Mike Griese
8bcf0a936f More float-math to try and make this work 2020-04-13 11:08:51 -05:00
Mike Griese
50a78bb7ea Making everything floats seemed to make things better, but didn't totally fix the problem unfortunately.
See #4591
2020-04-13 09:47:32 -05:00
Carlos Zamora
ea1bb2ed93 Add 'copyFormatting' global setting (#5299)
## Summary of the Pull Request
Implements `copyFormatting` as a global setting. When enabled, formatting such as font and foreground/background colors are copied to the clipboard on _all_ copy operations.

Also updates the schema and docs.

## References
#5212 - Spec for Formatted Copying
#4191 - Setting to enable/disable formatted copy

#5263 - PR prematurely merged without approval of #5212 

This feature will also have an impact on these yet-to-be-implemented features:
- #5262 - copyFormatting Keybinding Arg for Copy
- #1553 - Pointer Bindings
- #4191 - add array support for `copyFormatting`


## Detailed Description of the Pull Request
We already check if the hstring passed into the clipboard is empty before setting it. So the majority of the changes are actually just adding the global setting in.

## Validation Steps Performed
| `copyFormatting` | Mouse Copy | Keyboard Copy |
|--|--|--|
| not set (`false`) | ✔ | ✔ |
| `true` | ✔ | ✔ |
| `false` | ✔ | ✔ |
2020-04-09 23:32:38 +00:00
Kayla Cinnamon
08df4fc27a doc: add a spec for the change to formatted copying (#5212)
This is the spec that goes into what we do with HTML copy once we set the
default copy behavior to plain text.

Specs #4191 

Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
Co-authored-by: Josh Soref <jsoref@users.noreply.github.com>
Co-authored-by: Leon Liang <57155886+leonMSFT@users.noreply.github.com>
2020-04-09 16:30:19 -07:00
Mike Griese
6f0f245dd0 Don't wait for a connection to finish when a control is closed (#5303)
## Summary of the Pull Request

When a pane is closed by a connection, we want to wait until the connection is actually `Closed` before we fire the actual `Closed` event. If the connection didn't close gracefully, there are scenarios where we want to print a message to the screen.

However, when a pane is closed by the UI, we don't really care to wait for the connection to be completely closed. We can just do it whenever. So I've moved that call to be on a background thread.

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

## Detailed Description of the Pull Request / Additional comments
Previously we'd wait for the connection to close synchronously when closing tabs or panes. For misbehaving applications like `ssh.exe`, that could result in the `Close` needing to `WaitForSingleObject` _on the UI thread_. If the user closed the tab / pane either with a keybinding or with some other UI element, they don't really care to see the error message anymore. They just want the pane closed. So there's no need to wait for the actual connection to close - the app can just continue on with whatever it was doing.

## Validation Steps Performed
Messed around with closing tabs, panes, tabs with many panes, the entire window. Did this with keybindings, or by clicking on the 'x' on the tab, the 'x' on the window, or using middle-click.

I'm always scared of things like this, so there's a 50% chance this makes things horribly worse.
2020-04-09 23:27:56 +00:00
Carlos Zamora
c65de31172 Update default settings.json experience (#5217)
## Summary of the Pull Request
Add comments and settings to settings.json for discoverability.

## PR Checklist
* [X] Closes #5187 
* [X] Closes #5188 
* [X] Closes #3058
2020-04-09 23:21:22 +00:00
Carlos Zamora
2bd1e398bd Add default values to defaults.json (#5231)
## Summary of the Pull Request
This updates defaults.json to include the default values for all global and profile settings. Most default keybinding args are added too. This also updates a few outdated items found in the docs.

## PR Checklist
* [X] Closes #5189 

## Validation Steps Performed
After making the changes, I made sure all of the settings are deserialized by debugging and stepping through the `LayerJson` code.
- [X] Global Settings

I was mainly looking for two things:
- the key/value pair is found and read
- the value did not change before/after the pair was read
2020-04-09 23:13:57 +00:00
Dustin L. Howett (MSFT)
a09989749a Fall back to TerminalApp.dll's version when we're unpackaged (#5274)
This pull request makes sure we still get a usable (for troubleshooting purposes) version number in the about dialog and settings file when the user is running unpackaged.

This introduces a magic LCID constant (0x0409).B y default, Package ES emits
version resource information that says we're localized to ... language zero.
It also emits a language-coded version block for 0x0409 (en-US).

These two things cannot both be true. Collapse the wave function by hardcoding
0x0409.
2020-04-09 15:59:21 -07:00
James Holderness
2d09dfd48b Add support for the DSR-OS operating status report (#5300)
This adds support for the VT escape sequence that requests the
terminal's operating status. There is no attempt to actually verify the
status of the app, though. We always return a response indicating a good
operating condition (the same as most terminal emulators).

## PR Checklist
* [x] CLA signed.
* [x] Tests added/passed

## Detailed Description of the Pull Request / Additional comments

This required an update to the `OutputStateMachineEngine` to accept the
`DSR-OS` type, since it only dispatches types that it recognises (I
think that's unnecessary, but that's an issue for another day).

The actual processing of the request is handled in the `AdaptDispatch`
class, where it simply responds with a hard coded sequence (`CSI 0 n`),
indicating a good operating condition.

## Validation Steps Performed

I've added unit tests to confirm that the request is dispatched
correctly, and the appropriate response is returned. I've also manually
confirmed that the test of the _Device Status Report_ in _Vttest_ is now
succeeding.

Closes #5052
2020-04-09 13:11:37 -07:00
Dustin L. Howett (MSFT)
7936605cc9 Control: make sure we actually have a dispatcher in 100ms (#5292)
This fixes a silly regression introduced by 1299a839b. We would crash
when the connection updated the cursor as it was being torn down.
2020-04-09 12:08:06 -07:00
Dustin L. Howett (MSFT)
edf79b281a Revert "Add 'copyFormatting' global setting (#5263)" (#5298)
This reverts commit c55d9cd62d.
It was merged with its corresponding specification unmerged.
2020-04-09 11:13:21 -07:00
Carlos Zamora
c55d9cd62d Add 'copyFormatting' global setting (#5263)
## Summary of the Pull Request
Implements `copyFormatting` as a global setting. When enabled, formatting such as font and foreground/background colors are copied to the clipboard on _all_ copy operations.

Also updates the schema and docs.

## References
#5212 - Spec for Formatted Copying
#4191 - Setting to enable/disable formatted copy

This feature will also have an impact on these yet-to-be-implemented features:
- #5262 - copyFormatting Keybinding Arg for Copy
- #1553 - Pointer Bindings


## PR Checklist
* [X] Closes #4191

## Detailed Description of the Pull Request / Additional comments
We already check if the hstring passed into the clipboard is empty before setting it. So the majority of the changes are actually just adding the global setting in.

## Validation Steps Performed
| `copyFormatting` | Mouse Copy | Keyboard Copy |
|--|--|--|
| not set (`false`) | ✔ | ✔ |
| `true` | ✔ | ✔ |
| `false` | ✔ | ✔ |
2020-04-09 18:03:32 +00:00
Kayla Cinnamon
191b0d288a doc: update list of team members in README (#5296) 2020-04-09 10:38:12 -07:00
Dustin L. Howett (MSFT)
1299a839bd Muffle updates to the cursor position to 1/~100ms (#5289)
This stops us from dispatching back-to-back terminal cursor position
updates to the TSF control before it has a chance to get back to us.

Fixes #5288.
2020-04-08 17:20:51 -07:00
Mike Griese
6fabc4abb7 Fix copying wrapped lines by implementing better scrolling (#5181)
Now that the Terminal is doing a better job of actually marking which
lines were and were not wrapped, we're not always copying lines as
"wrapped" when they should be. We're more correctly marking lines as not
wrapped, when previously we'd leave them marked wrapped.

The real problem is here in the `ScrollFrame` method - we'd manually
newline the cursor to make the terminal's viewport shift down to a new
line. If we had to scroll the viewport for a _wrapped_ line, this would
cause the Terminal to mark that line as broken, because conpty would
emit an extra `\n` that didn't actually exist.

This more correctly implements `ScrollFrame`. Now, well move where we
"thought" the cursor was, so when we get to the next `PaintBufferLine`,
if the cursor needs to newline for the next line, it'll newline, but if
we're in the middle of a wrapped line, we'll just keep printing the
wrapped line.

A couple follow up bugs were found to be caused by the same bad logic.
See #5039 and #5161 for more details on the investigations there.

## References

* #4741 RwR, which probably made this worse
* #5122, which I branched off of 
* #1245, #357 - a pair of other conpty wrapped lines bugs
* #5228 - A followup issue for this PR

## PR Checklist
* [x] Closes #5113
* [x] Closes #5180 (by fixing DECRST 25)
* [x] Closes #5039
* [x] Closes #5161 (by ensuring we only `removeSpaces` on the actual
  bottom line)
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed

* Checked the cases from #1245, #357 to validate that they still work
* Added more and more tests for these scenarios, and then I added MORE
  tests
* The entire team played with this in selfhost builds
2020-04-09 00:06:25 +00:00
Dustin L. Howett (MSFT)
5622f230a8 Control: Start the connection outside of lock (#5286)
When the connection printed text immediately, synchronously, as part of
Start() it would cause terminal to deadlock. We should start the
connection outside of lock.

The ConptyConnection would do this when it failed to launch something
(trivial repro: `wt -- xyz`).

The TelnetConnection would do this all the time, because local loopback
telnet is fast and easy.
2020-04-08 13:55:20 -07:00
Dustin L. Howett (MSFT)
761bee80c0 Localize the profile name for cmd, change it to Command Prompt (#5287)
This commit introduces another template replacement for the user's
default settings, COMMAND_PROMPT_LOCALIZED_NAME, which will be replaced
with the contents of the CommandPromptDisplayName resource.

By default, that will be "Command Prompt." This name change will apply
only for new users, and only on first launch. Changes in the system
locale after first launch will not impact the name of the profile.

If the user _removes_ the name from their command prompt profile, its
name will revert irrecoverably to "Command Prompt". They will not be
given a chance to regenerate a localized name.

Fixes #4476.
2020-04-08 13:41:58 -07:00
Josh Soref
26778440b3 ci: spelling: update to 0.0.14a (#5270) 2020-04-07 14:58:12 -07:00
Dustin L. Howett (MSFT)
7335232cda ci/spelling: add ecma to the allowlist (#5275) 2020-04-07 14:39:21 -07:00
Carlos Zamora
7c7db79782 Rename 'requestedTheme' to 'theme' (#5265)
## Summary of the Pull Request

Renames the `requestedTheme` global setting to `theme`. Propagates updates to...
- schema
- doc
- defaults.json
- universal-defaults.json
 
## PR Checklist
* [X] Closes #5264 

## Validation Steps Performed
| `theme` | Success? |
|--|--|
| `system` | ✔ |
| `light` | ✔ |
| `dark` | ✔ |

But we really know that `dark` is the one we care about here 😉
2020-04-07 19:14:06 +00:00
Leonard Hecker
a9c9714295 Delegate all character input to the character event handler (#4192)
My basic idea was that `WM_CHAR` is just the better `WM_KEYDOWN`.
The latter fails to properly support common dead key sequences like in
#3516.

As such I added some logic to `Terminal::SendKeyEvent` to make it return
false if the pressed key represents a printable character.
This causes us to receive a character event with a (hopefully) correctly
composed code unit, which then gets sent to `Terminal::SendCharEvent`.
`Terminal::SendCharEvent` in turn had to be modified to support
potentially pressed modifier keys, since `Terminal::SendKeyEvent` isn't
doing that for us anymore.
Lastly `TerminalInput` had to be modified heavily to support character
events with modifier key states. In order to do so I merged its
`HandleKey` and `HandleChar` methods into a single one, that now handles
both cases.
Since key events will now contain character data and character events
key codes the decision logic in `TerminalInput::HandleKey` had to be
rewritten.

## PR Checklist
* [x] CLA signed
* [x] Tests added/passed
* [x] I've discussed this with core contributors already.

## Validation Steps Performed

* See #3516.
* I don't have any keyboard that generates surrogate characters. Due to
  this I modified `TermControl::_SendPastedTextToConnection` to send the
  data to `_terminal->SendCharEvent()` instead. I then pasted the test
  string ""𐐌𐐜𐐬" and ensured that the new `TerminalInput::_SendChar`
  method still correctly assembles surrogate pairs.

Closes #3516
Closes #3554 (obsoleted by this PR)
Potentially impacts #391, which sounds like a duplicate of #3516
2020-04-07 19:09:28 +00:00
Dustin L. Howett (MSFT)
52d6c03e64 Patch the default profile and version into the settings template (#5232)
This pull request introduces unexpanded variables (`%DEFAULT_PROFILE%`,
`%VERSION%` and `%PRODUCT%`) to the user settings template and code to
expand them.

While doing this, I ran into a couple things that needed to widen from
accepting strings to accepting string views. I also had to move
application name and version detection up to AppLogic and expose the
AppLogic singleton.

The dynamic profile generation logic had to be moved to before we inject
the templated variables, as the new default profile depends on the
generated dynamic profiles.

References #5189, #5217 (because it has a dependency on `VERSION` and
`PRODUCT`).

## PR Checklist
* [x] Closes #2721 
* [x] CLA signed
* [x] Tests added/passed
* [ ] Requires documentation to be updated
* [x] I've discussed this with core contributors already

## Validation Steps Performed
Deleted my settings and watched them regenerate.
2020-04-07 11:35:05 -07:00
Dustin L. Howett (MSFT)
e44dc2bcf8 Relax locking around the swapchain (#5225)
The terminal lock is really only for the terminal; since the renderer is
fully owned by the control, not the Terminal, and we'll only be
receiving swap chain events after we register them during
initialization, we don't need to lock before _or_ after firing off the
coroutine.

Fixes #5203.
2020-04-06 17:15:31 -07:00
James Holderness
38803d7d05 Make RIS switch back to the main buffer (#5248)
## Summary of the Pull Request

If we receive a _Reset to Initial State_ (`RIS`) sequence while in the alternate screen buffer, we should be switching back to the main buffer. This PR fixes that behavior.

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

I've added a condition at the start of the `AdaptDispatch::HardReset` method to check whether we're using the alt buffer, and if so, call the `ConGetSet::PrivateUseMainScreenBuffer` API to switch back to the main buffer.

Calling `AdaptDispatch::UseMainScreenBuffer` would probably be neater for this, but it would also attempt to restore the cursor state, which seems pointless when we're in the process of resetting everything anyway.

## Validation Steps Performed

I've added a screen buffer test to confirm that the `RIS` sequence does actually switch back to the main buffer. I've also manually confirmed that the test case in issue #3685 does now behave as expected.
2020-04-06 23:44:55 +00:00
James Holderness
6d4c44f3a4 Prevent the cursor type being reset when changing the visibility (#5251)
A side effect of the `SetConsoleCursorInfo` API is that it resets the
cursor type to _Legacy_. This makes it impossible to change the cursor
visibility via the console APIs without also resetting the user's
preferred cursor type. This PR attempts to fix that limitation, by only
resetting the cursor type if the size has also been changed.

## PR Checklist
* [x] Closes #4124
* [x] CLA signed
* [ ] Tests added/passed
* [ ] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

I suspect the reason for the original behaviour was because the
`SetConsoleCursorInfo` API sets both the visibility and the size, and if
you're setting the size, it's assumed you'd want the _Legacy_ cursor
type, because that's the only style for which the size is applicable.

So my solution was to only reset the cursor type if the requested cursor
size was actually different from the current size. That should be
reasonably backwards compatible with most size-changing code, but also
allow for changing the visibility without resetting the cursor type.

## Validation Steps Performed

I've tested the example code from issue #4124, and confirmed that it now
works correctly without resetting the cursor type.

I've also tested the console's _mark mode_, which temporarily changes
the cursor size while selecting. I've confirmed that the size still
changes, and that the original cursor type is restored afterwards.
2020-04-06 16:00:40 -07:00
Michael Niksa
b1a5604b55 Split rolling and PR builds. Drop ARM64, X86 from PR. (#5256)
## Summary of the Pull Request
Edits the definition file to distinguish further between the rolling build (the one that happens in master as it's updated) and the PR builds (that happen on every push to a pull request to master.) We will build less in PR since it rolls so often by removing the lines that reveal very few to no bugs at PR time. We'll leave them on in rolling so stuff can still be caught.

## PR Checklist
* [x] Closes a desire to not waste builds.
* [x] I work here.
* [ ] We'll see if the build still works.
* [x] No specific docs.
* [x] I talked about this with @DHowett-MSFT already.

## Validation Steps Performed
* [x] This PR itself should validate that the definition still works in PRs. I think we have to wait for it to go to master to see if the trigger still works there.
2020-04-06 22:11:03 +00:00
Chester Liu
4f8acb4b9f Make CodepointWidthDetector::GetWidth faster (#3727)
This is a subset of #3578 which I think is harmless and the first step towards making things right.
References #3546 #3578 

## Detailed Description of the Pull Request / Additional comments

For more robust Unicode support, `CodepointWidthDetector` should provide concrete width information rather than a simple boolean of `IsWide`. Currently only `IsWide` is widely used and optimized using quick lookup table and fallback cache. This PR moves those optimization into `GetWidth`.

## Validation Steps Performed

API remains unchanged. Things are not broken.
2020-04-04 00:56:22 +00:00
Carlos Zamora
94215535cd doc: remove "globals" from schema (#5229)
## Summary of the Pull Request
Minor cleanup on the schema. Globals isn't accepted anymore,
so the schema should not help you autocomplete anymore.

## Validation Steps Performed
Imported the new schema. You do _not_ get a warning when globals
is in. But, the schema won't suggest things when inside globals.
It's just treated as an unknown item.

However, "defaultProfile" is still required (more of a sanity test)
2020-04-03 13:58:56 -07:00
Carlos Zamora
286af380c9 Add more object ID tracing for Accessibility (#5215)
## Summary of the Pull Request

In preparation for getting more accessibility-related issues, I added an ID to the `ScreenInfoUiaProvider` (SIUP) and abstracted the one from `UiaTextRange`. Using this, I noticed that we are creating SIUPs when a new tab/pane is created. This is _good_. This means that we need to somehow notify a UIA Client that out structure has changed, and we need to use the new SIUP because the old one has been removed.

I'll be investigating that more after this PR lands.
2020-04-03 20:06:47 +00:00
Dustin L. Howett (MSFT)
9513d543b7 Force all our dialogs into the application theme, forcibly (#5224)
Because we cannot set RequestedTheme at the application level, we
occasionally run into issues where parts of our UI end up themed
incorrectly.  Dialogs, for example, live under a different Xaml root
element than the rest of our application. This makes our popup menus and
buttons "disappear" when the user wants Terminal to be in a different
theme than the rest of the system.  This hack---and it _is_ a
hack--walks up a dialog's ancestry and forces the theme on each element
up to the root. We're relying a bit on Xaml's implementation details
here, but it does have the desired effect.

It's not enough to set the theme on the dialog alone.

Fixes #3654.
Fixes #5195.
2020-04-03 15:55:04 +00:00
Kayla Cinnamon
ecc6c89dde Add splitMode to JSON schema (#5123)
## Summary of the Pull Request
Added `splitMode` to settings schema JSON and md files.
The definition might need some tweaking.

## References

## PR Checklist
* [x] Closes #4939
2020-04-03 00:11:31 +00:00
Kayla Cinnamon
f57ed0518a Change aka.ms links to fwlinks (#5223) 2020-04-02 16:19:03 -07:00
Carlos Zamora
084b48a751 Rename copy keybinding arg (#5216)
## Summary of the Pull Request
`TrimWhitespace` is misleading. So it is now renamed as 'singleLine'. If true, it comes out as a single line! That makes more sense!

## PR Checklist
* [X] Closes #3824 

## Validation Steps Performed
Attempted the keybinding with both settings (and none set).
Attempted mouse copy with and without shift.
2020-04-02 23:10:28 +00:00
Mike Griese
90a78d65ac Add null, unbound to schema for keybindings (#5221)
## Summary of the Pull Request

Add `null`, `unbound` to schema for keybindings

## PR Checklist
* [x] Closes #4751
* [x] I work here
* [n/a] Tests added/passed
* [x] Requires documentation to be updated
2020-04-02 14:43:46 -05:00
Mike Griese
bdbf40451e This fixes C-M-space for WSL but not for Win32, but I'm not sure there's a problem in Win32 quite yet. (#5208)
## Summary of the Pull Request

When conpty is in VT input mode, we pass through all the input we receive. This includes all the other `Action*Dispatch` methods, but missed this one.

## References
* Missed during #4856 
* Discovered during the course of the #4192 review
* #5205 Also investigated part of the issue, but found a different bug.

## PR Checklist
* [x] Doesn't close anything, just related to above things.
* [x] I work here
* [ ] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments
This will fix the <kbd>ctrl+alt+space</kbd> in the Terminal thing mentioned in #4192, but doesn't actually resolve the root cause of that bug (which is tracked in #5205).
2020-04-01 22:31:08 +00:00
Mike Griese
584e4498ee Use MONITOR_DEFAULTTONEAREST so we can restore properly from minimized (#5213)
## Summary of the Pull Request

When we're restoring from minimized, that `MonitorFromWindow` call is returning null. Curious that we're getting null here, when [MSDN states](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow):
> If the window is currently minimized, MonitorFromWindow uses the rectangle of the window before it was minimized.

Turns out, `MONITOR_DEFAULTTONEAREST` just fixes this.

## References
* #4857 - original PR that added this code block

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

## Validation Steps Performed
Verified manually
2020-04-01 21:19:04 +00:00
Josh Soref
713550d56c ci: spelling: update to 0.0.13 and include advice (#5211) 2020-04-01 12:15:42 -07:00
Mike Griese
9409e851d0 ci: remove "wether" from dictionary, add "VTE" (#5207)
_Technically_, "wether" is a word but I'd be shocked if there's a scenario for
us to use it properly in this repo, so I'm pulling it from the dictionary.

Also, in #5200, we added "VTE", which is totally a valid acronym, to the codebase,
but not the whitelist. I'm not sure why the bot let me merge it anyways, but I'm
fixing it now.
2020-04-01 12:15:20 -07:00
Dustin L. Howett (MSFT)
64489b1ec1 rename profiles.json to settings.json, clean up the defaults (#5199)
This pull request migrates `profiles.json` to `settings.json` and removes the legacy roaming AppData settings migrator.

It also:

* separates the key bindings in defaults.json into logical groups
* syncs the universal terminal defaults with the primary defaults
* removes some stray newlines that ended up at the beginning of settings.json and defaults.json

Fixes #5186.
Fixes #3291.

### categorize key bindings

### sync universal with main

### kill stray newlines in template files

### move profiles.json to settings.json

This commit also changes Get*Settings from returning a string to
returning a std::filesystem::path. We gain in expressiveness without a
loss in clarity (since path still supports .c_str()).

NOTE: I tried to do an atomic rename with the handle open, but it didn't
work for reparse points (it moves the destination of a symbolic link
out into the settings folder directly.)

(snip for atomic rename code)

```c++
auto path{ pathToSettingsFile.wstring() };
auto renameBufferSize{ sizeof(FILE_RENAME_INFO) + (path.size() * sizeof(wchar_t)) };
auto renameBuffer{ std::make_unique<std::byte[]>(renameBufferSize) };
auto renameInfo{ reinterpret_cast<FILE_RENAME_INFO*>(renameBuffer.get()) };
renameInfo->Flags = FILE_RENAME_FLAG_REPLACE_IF_EXISTS | FILE_RENAME_FLAG_POSIX_SEMANTICS;
renameInfo->RootDirectory = nullptr;
renameInfo->FileNameLength = gsl::narrow_cast<DWORD>(path.size());
std::copy(path.cbegin(), path.cend(), std::begin(renameInfo->FileName));

THROW_IF_WIN32_BOOL_FALSE(SetFileInformationByHandle(hLegacyFile.get(),
                          FileRenameInfo,
                          renameBuffer.get(),
                          gsl::narrow_cast<DWORD>(renameBufferSize)));
```

(end snip)

### Stop resurrecting dead roaming profiles
2020-04-01 19:09:42 +00:00
Mike Griese
c1463bdbf0 doc: fix a typo in dynamic profiles (#5206) 2020-04-01 11:59:20 -07:00
Mike Griese
a12a6285f5 Manually pass mouse wheel messages to TermControls (#5131)
## Summary of the Pull Request

As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads).

This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content.

Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. 

## References

* #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML

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

## Detailed Description of the Pull Request / Additional comments

I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point`

## Validation Steps Performed

* It works on my HP Spectre 2017 with a synaptics trackpad
  - I also made sure to test that `tmux` works in panes on this laptop
* It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 16:58:16 +00:00
Carlos Zamora
f8227e6fa2 Reduce CursorChanged Events for Accessibility (#5196)
## Summary of the Pull Request
Reduce the number of times we dispatch a cursor changed event. We were firing it every time the renderer had to do anything related to the cursor. Unfortunately, blinking the cursor triggered this behavior. Now we just check if the position has changed.

## PR Checklist
* [X] Closes #5143


## Validation Steps Performed
Verified using Narrator
Also verified #3791 still works right
2020-04-01 15:56:20 +00:00
James Holderness
8585bc6bde Clamp parameter values to a maximum of 32767. (#5200)
## Summary of the Pull Request

This PR clamps the parameter values in the VT `StateMachine` parser to 32767, which was the initial limit prior to PR #3956. This fixes a number of overflow bugs (some of which could cause the app to crash), since much of the code is not prepared to handle values outside the range of a `short`.

## References

#3956 - the PR where the cap was changed to the range of `size_t`
#4254 - one example of a crash caused by the higher range

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

The DEC STD 070 reference recommends supporting up to at least 16384 for parameter values, so 32767 should be more than enough for any standard VT sequence. It might be nice to increase the limit to 65535 at some point, since that is the cap used by both XTerm and VTE. However, that is not essential, since there are very few situations where you'd even notice the difference. For now, 32767 is the safest choice for us, since anything greater than that has the potential to overflow and crash the app in a number of places.

## Validation Steps Performed

I had to make a couple of modifications to the range checks in the `OutputEngineTest`, more or less reverting to the pre-#3956 behavior, but after that all of the unit tests passed as expected.

I manually confirmed that this fixes the hanging test case from #5160, as well as overflow issues in the cursor operations, and crashes in `IL` and `DL` (see https://github.com/microsoft/terminal/issues/4254#issuecomment-575292926).
2020-04-01 12:49:57 +00:00
James Holderness
9a0b6e3b69 Reimplement the VT tab stop functionality (#5173)
## Summary of the Pull Request

This is essentially a rewrite of the VT tab stop functionality, implemented entirely within the `AdaptDispatch` class. This significantly simplifies the `ConGetSet` interface, and should hopefully make it easier to share the functionality with the Windows Terminal VT implementation in the future.

By removing the dependence on the `SCREEN_INFORMATION` class, it fixes the problem of the the tab state not being preserved when switching between the main and alternate buffers. And the new architecture also fixes problems with the tabs not being correctly initialized when the screen is resized.

## References

This fixes one aspect of issue #3545.
It also supersedes the fix for #411 (PR #2816).
I'm hoping the simplification of `ConGetSet` will help with #3849.

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

In the new tab architecture, there is now a `vector<bool>` (__tabStopColumns_), which tracks whether any particular column is a tab stop or not. There is also a __initDefaultTabStops_ flag indicating whether the default tab stop positions need to be initialised when the screen is resized.

The way this works, the vector is initially empty, and only initialized (to the current width of the screen) when it needs to be used. When the vector grows in size, the __initDefaultTabStops_ flag determines whether the new columns are set to false, or if every 8th column is set to true.

By default we want the latter behaviour - newly revealed columns should have default tab stops assigned to them - so __initDefaultTabStops_ is set to true. However, after a `TBC 3` operation (i.e. we've cleared all tab stops), there should be no tab stops in any newly revealed columns, so __initDefaultTabStops_ is set to false.

Note that the __tabStopColumns_ vector is never made smaller when the window is shrunk, and that way it can preserve the state of tab stops that are off screen, but which may come into range if the window is made bigger again.

However, we can can still reset the vector completely after an `RIS` or `TBC 3` operation, since the state can then be reconstructed automatically based on just the __initDefaultTabStops_ flag.

## Validation Steps Performed

The original screen buffer tests had to be rewritten to set and query the tab stop state using escape sequences rather than interacting with the `SCREEN_INFORMATION` class directly, but otherwise the structure of most tests remained largely the same.

However, the alt buffer test was significantly rewritten, since the original behaviour was incorrect, and the initialization test was dropped completely, since it was no longer applicable. The adapter tests were also dropped, since they were testing the `ConGetSet` interface which has now been removed.

I also had to make an addition to the method setup of the screen buffer tests (making sure the viewport was appropriately initialized), since there were some tests (unrelated to tab stops) that were previously dependent on the state being set in the tab initialization test which has now been removed.

I've manually tested the issue described in #4669 and confirmed that the tabs now produce the correct spacing after a resize.
2020-04-01 12:49:27 +00:00
Mike Griese
a621a6fabb Use an Automatic split for splitPane by default (#5194)
## Summary of the Pull Request

You no longer _need_ to specify the `split` argument to `splitPane`, it will default to `Automatic` instead of `None`


## PR Checklist
* [x] Closes a discussion we had in team sync
* [x] I work here
* [x] Tests updated
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

Also disables the tests that are broken in #5169 while I investigate
2020-04-01 00:59:31 +00:00
Dustin L. Howett (MSFT)
cd9e854553 Remove a heap of legacy settings deserialization (#5190)
This commit removes support for:

* legacy keybindings of all types
* `colorScheme.colors`, as an array
* A `globals` object in the root of the settings file
* `profile.colorTable` and `profile.colorscheme` (the rare v0.1 all-lowercase variety)

Fixes #4091.
Fixes #1069.
2020-03-31 13:58:28 -07:00
Leon Liang
ca6b54e652 Redraw TSFInputControl when Terminal cursor updates (#5135)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
This PR will allow TSFInputControl to redraw its Canvas and TextBlock in response to when the Terminal cursor position updates. This will fix the issue where during Korean composition, the first symbol of the next composition will appear on top of the previous composed character. Since the Terminal Cursor updates a lot, I've added some checks to see if the TSFInputControl really needs to redraw. This will also decrease the number of actual redraws since we receive a bunch of `LayoutRequested` events when there's no difference between them.

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

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Startup, teardown, CJK IME gibberish testing, making sure the IME block shows up in the right place.
2020-03-30 23:21:47 +00:00
163 changed files with 5847 additions and 483348 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,7 +0,0 @@
mfcribbon
microsoft
microsoftonline
osgvsowi
powershell
tdbuildteamid
visualstudio

View File

@@ -1,61 +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$
^dep/
^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,7 +0,0 @@
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
(?:0[Xx]|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]?\b
\{[0-9A-FA-F]{8}-(?:[0-9A-FA-F]{4}-){3}[0-9A-FA-F]{12}\}
\d+x\d+Logo
Scro\&ll
# selectionInput.cpp
:\\windows\\syste\b

View File

@@ -1,7 +0,0 @@
The contents of each `.txt` file in this directory are merged together.
* [alphabet](alphabet.txt) is a sample for alphabet related items
* [web](web.txt) is a sample for web/html related items
* [whitelist](whitelist.txt) is the main whitelist -- there is nothing
particularly special about the file name (beyond the extension which is
important).

View File

@@ -1,3 +0,0 @@
http
td
www

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,55 +1,91 @@
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
Pham
Rincewind
rprichard
Schoonover
shadertoy
Shomnipotence
simioni
Somuah
sonph
sonpham
stakx
talo
thereses
Walisch
WDX
Wellons
Wirt
Wojciech
zadjii
Zamor
Zamora
zamora
Zoey
zorio
Zverovich

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

@@ -0,0 +1,13 @@
The contents of each `.txt` file in this directory are merged together.
* [alphabet](alphabet.txt) is a sample for alphabet related items
* [web](web.txt) is a sample for web/html related items
* [expect](expect.txt) is the main list of expected items -- there is nothing
particularly special about the file name (beyond the extension which is
important).
These terms are things which temporarily exist in the project, but which
aren't necessarily words.
If something is a word that could come and go, it probably belongs in a
[dictionary](../dictionary/README.md).

View File

@@ -1,27 +1,33 @@
AAAa
AAAAA
AAAAAAAAAAAAA
AAAAAABBBBBBCCC
AAAAABBBBBBCCC
abcd
abcd
abcde
abcdef
ABCDEFG
ABCDEFGH
ABCDEFGHIJ
abcdefghijk
ABCDEFGHIJKLMNO
abcdefghijklmnop
ABCDEFGHIJKLMNOPQRST
abcdefghijklmnopqrstuvwxyz
QQQQQ
QQQQQQQQQ
QQQQQQQQQQ
ABCG
ABE
abf
BBBBB
BBBBBBBB
BBBBBCCC
BBBBCCCCC
BBGGRR
EFG
EFGh
QQQQQQQQQQABCDEFGHIJ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
qrstuvwxyz
qwerty
QWERTYUIOP
qwertyuiopasdfg
TTTTTTTTTTTTTTTTTTTTTTTTTT
VVVVVVVVVVVVVVVV
yyyy
YYYYYYYDDDDDDDDDDD
ZAAZZ
ZABBZ
ZBAZZ

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.12-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

@@ -150,7 +150,6 @@ Please file new issues, feature requests and suggestions, but **DO search for si
If you would like to ask a question that you feel doesn't warrant an issue (yet), please reach out to us via Twitter:
* Kayla Cinnamon, Program Manager: [@cinnamon\_msft](https://twitter.com/cinnamon_msft)
* Rich Turner, Program Manager: [@richturn\_ms](https://twitter.com/richturn_ms)
* Dustin Howett, Engineering Lead: [@dhowett](https://twitter.com/DHowett)
* Michael Niksa, Senior Developer: [@michaelniksa](https://twitter.com/MichaelNiksa)
* Mike Griese, Developer: [@zadjii](https://twitter.com/zadjii)

View File

@@ -27,21 +27,43 @@ variables:
# 0.0.1904.0900
name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr)
jobs:
- template: ./templates/build-console-audit-job.yml
parameters:
platform: x64
stages:
- stage: Audit_x64
displayName: Audit Mode
dependsOn: []
condition: succeeded()
jobs:
- template: ./templates/build-console-audit-job.yml
parameters:
platform: x64
- stage: Build_x64
displayName: Build x64
dependsOn: []
condition: succeeded()
jobs:
- template: ./templates/build-console-ci.yml
parameters:
platform: x64
- stage: Build_x86
displayName: Build x86
dependsOn: []
condition: not(eq(variables['Build.Reason'], 'PullRequest'))
jobs:
- template: ./templates/build-console-ci.yml
parameters:
platform: x86
- stage: Build_ARM64
displayName: Build ARM64
dependsOn: []
condition: not(eq(variables['Build.Reason'], 'PullRequest'))
jobs:
- template: ./templates/build-console-ci.yml
parameters:
platform: ARM64
- stage: Scripts
displayName: Code Health Scripts
dependsOn: []
condition: succeeded()
jobs:
- template: ./templates/check-formatting.yml
- template: ./templates/build-console-ci.yml
parameters:
platform: x64
- template: ./templates/build-console-ci.yml
parameters:
platform: x86
- template: ./templates/build-console-ci.yml
parameters:
platform: ARM64
- template: ./templates/check-formatting.yml

View File

@@ -7,13 +7,14 @@ Properties listed below affect the entire window, regardless of the profile sett
| -------- | --------- | ---- | ------- | ----------- |
| `alwaysShowTabs` | _Required_ | Boolean | `true` | When set to `true`, tabs are always displayed. When set to `false` and `showTabsInTitlebar` is set to `false`, tabs only appear after typing <kbd>Ctrl</kbd> + <kbd>T</kbd>. |
| `copyOnSelect` | Optional | Boolean | `false` | When set to `true`, a selection is immediately copied to your clipboard upon creation. When set to `false`, the selection persists and awaits further action. |
| `copyFormatting` | Optional | Boolean | `false` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. |
| `defaultProfile` | _Required_ | String | PowerShell guid | Sets the default profile. Opens by typing <kbd>Ctrl</kbd> + <kbd>T</kbd> or by clicking the '+' icon. The guid of the desired default profile is used as the value. |
| `initialCols` | _Required_ | Integer | `120` | The number of columns displayed in the window upon first load. |
| `initialPosition` | Optional | String | `","` | The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If `launchMode` is set to `"maximized"`, the window will be maximized on the monitor specified by those coordinates. |
| `initialRows` | _Required_ | Integer | `30` | The number of rows displayed in the window upon first load. |
| `launchMode` | Optional | String | `default` | Defines whether the Terminal will launch as maximized or not. Possible values: `"default"`, `"maximized"` |
| `rowsToScroll` | Optional | Integer | `system` | The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or "system". |
| `requestedTheme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` |
| `theme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` |
| `showTerminalTitleInTitlebar` | _Required_ | Boolean | `true` | When set to `true`, titlebar displays the title of the selected tab. When set to `false`, titlebar displays "Windows Terminal". |
| `showTabsInTitlebar` | Optional | Boolean | `true` | When set to `true`, the tabs are moved into the titlebar and the titlebar disappears. When set to `false`, the titlebar sits above the tabs. |
| `snapToGridOnResize` | Optional | Boolean | `false` | When set to `true`, the window will snap to the nearest character boundary on resize. When `false`, the window will resize "smoothly" |
@@ -37,7 +38,6 @@ Properties listed below are specific to each unique profile.
| `backgroundImageStretchMode` | Optional | String | `uniformToFill` | Sets how the background image is resized to fill the window. Possible values: `"none"`, `"fill"`, `"uniform"`, `"uniformToFill"` |
| `closeOnExit` | Optional | String | `graceful` | Sets how the profile reacts to termination or failure to launch. Possible values: `"graceful"` (close when `exit` is typed or the process exits normally), `"always"` (always close) and `"never"` (never close). `true` and `false` are accepted as synonyms for `"graceful"` and `"never"` respectively. |
| `colorScheme` | Optional | String | `Campbell` | Name of the terminal color scheme to use. Color schemes are defined under `schemes`. |
| `colorTable` | Optional | Array[String] | | Array of colors used in the profile if `colorscheme` is not set. Array follows the format defined in `schemes`. |
| `commandline` | Optional | String | | Executable used in the profile. |
| `cursorColor` | Optional | String | | Sets the cursor color of the profile. Overrides `cursorColor` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `cursorHeight` | Optional | Integer | | Sets the percentage height of the cursor starting from the bottom. Only works when `cursorShape` is set to `"vintage"`. Accepts values from 25-100. |
@@ -49,12 +49,12 @@ Properties listed below are specific to each unique profile.
| `historySize` | Optional | Integer | `9001` | The number of lines above the ones displayed in the window you can scroll back to. |
| `icon` | Optional | String | | Image file location of the icon used in the profile. Displays within the tab and the dropdown menu. |
| `padding` | Optional | String | `8, 8, 8, 8` | Sets the padding around the text within the window. Can have three different formats: `"#"` sets the same padding for all sides, `"#, #"` sets the same padding for left-right and top-bottom, and `"#, #, #, #"` sets the padding individually for left, top, right, and bottom. |
| `scrollbarState` | Optional | String | | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` |
| `scrollbarState` | Optional | String | `"visible"` | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` |
| `selectionBackground` | Optional | String | | Sets the selection background color of the profile. Overrides `selectionBackground` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `snapOnInput` | Optional | Boolean | `true` | When set to `true`, the window will scroll to the command input line when typing. When set to `false`, the window will not scroll when you start typing. |
| `source` | Optional | String | | Stores the name of the profile generator that originated this profile. _There are no discoverable values for this field._ |
| `startingDirectory` | Optional | String | `%USERPROFILE%` | The directory the shell starts in when it is loaded. |
| `suppressApplicationTitle` | Optional | Boolean | | When set to `true`, `tabTitle` overrides the default title of the tab and any title change messages from the application will be suppressed. When set to `false`, `tabTitle` behaves as normal. |
| `suppressApplicationTitle` | Optional | Boolean | `false` | When set to `true`, `tabTitle` overrides the default title of the tab and any title change messages from the application will be suppressed. When set to `false`, `tabTitle` behaves as normal. |
| `tabTitle` | Optional | String | | If set, will replace the `name` as the title to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. |
| `useAcrylic` | Optional | Boolean | `false` | When set to `true`, the window will have an acrylic background. When set to `false`, the window will have a plain, untextured background. The transparency only applies to focused windows due to OS limitation. |
| `experimental.retroTerminalEffect` | Optional | Boolean | `false` | When set to `true`, enable retro terminal effects. This is an experimental feature, and its continued existence is not guaranteed. |
@@ -111,14 +111,13 @@ For commands with arguments:
| Command | Command Description | Action (*=required) | Action Arguments | Argument Descriptions |
| ------- | ------------------- | ------ | ---------------- | ----------------- |
| `adjustFontSize` | Change the text size by a specified point amount. | `delta` | integer | Amount of size change per command invocation. |
| `closePane` | Close the active pane. | | | |
| `closeTab` | Close the current tab. | | | |
| `closeWindow` | Close the current window and all tabs within it. | | | |
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
| `decreaseFontSize` | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `singleLine` | boolean | When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text. |
| `duplicateTab` | Make a copy and open the current tab. | | | |
| `find` | Open the search dialog box. | | | |
| `increaseFontSize` | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| `newTab` | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| `nextTab` | Open the tab to the right of the current one. | | | |
@@ -132,7 +131,7 @@ For commands with arguments:
| `scrollUp` | Move the screen up. | | | |
| `scrollUpPage` | Move the screen up a whole page. | | | |
| `scrollDownPage` | Move the screen down a whole page. | | | |
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile`<br>7. `splitMode` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string<br>7. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name.<br>7. Controls how the pane splits. Only accepts `duplicate` which will duplicate the focused pane's profile into a new pane. |
| `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |
| `unbound` | Unbind the associated keys from any command. | | | |

View File

@@ -25,29 +25,15 @@
},
"ShortcutActionName": {
"enum": [
"adjustFontSize",
"closePane",
"closeTab",
"closeWindow",
"copy",
"copyTextWithoutNewlines",
"decreaseFontSize",
"duplicateTab",
"increaseFontSize",
"moveFocus",
"moveFocusDown",
"moveFocusLeft",
"moveFocusRight",
"moveFocusUp",
"newTab",
"newTabProfile0",
"newTabProfile1",
"newTabProfile2",
"newTabProfile3",
"newTabProfile4",
"newTabProfile5",
"newTabProfile6",
"newTabProfile7",
"newTabProfile8",
"nextTab",
"openNewTabDropdown",
"openSettings",
@@ -55,29 +41,15 @@
"prevTab",
"resetFontSize",
"resizePane",
"resizePaneDown",
"resizePaneLeft",
"resizePaneRight",
"resizePaneUp",
"scrollDown",
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"splitHorizontal",
"splitVertical",
"splitPane",
"switchToTab",
"switchToTab0",
"switchToTab1",
"switchToTab2",
"switchToTab3",
"switchToTab4",
"switchToTab5",
"switchToTab6",
"switchToTab7",
"switchToTab8",
"toggleFullscreen",
"find"
"find",
"unbound"
],
"type": "string"
},
@@ -135,6 +107,23 @@
],
"type": "object"
},
"AdjustFontSizeAction": {
"description": "Arguments corresponding to an Adjust Font Size Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "adjustFontSize" },
"delta": {
"type": "integer",
"default": 0,
"description": "How much to change the current font point size"
}
}
}
],
"required": [ "delta" ]
},
"CopyAction": {
"description": "Arguments corresponding to a Copy Text Action",
"allOf": [
@@ -142,10 +131,10 @@
{
"properties": {
"action": { "type": "string", "pattern": "copy" },
"trimWhitespace": {
"singleLine": {
"type": "boolean",
"default": true,
"description": "If true, whitespace is removed and newlines are maintained. If false, newlines are removed and whitespace is maintained."
"default": false,
"description": "If true, newlines are removed and whitespace is maintained. If false, whitespace is removed and newlines are maintained."
}
}
}
@@ -226,11 +215,14 @@
"$ref": "#/definitions/SplitState",
"default": "auto",
"description": "The orientation to split the pane in, either vertical (think [|]), horizontal (think [-]), or auto (splits pane based on remaining space)"
},
"splitMode": {
"default": "duplicate",
"description": "Control how the pane splits. Only accepts `duplicate` which will duplicate the focused pane's profile into a new pane."
}
}
}
],
"required": [ "split" ]
]
},
"Keybinding": {
"additionalProperties": false,
@@ -238,13 +230,15 @@
"command": {
"description": "The action executed when the associated key bindings are pressed.",
"oneOf": [
{ "$ref": "#/definitions/AdjustFontSizeAction" },
{ "$ref": "#/definitions/CopyAction" },
{ "$ref": "#/definitions/ShortcutActionName" },
{ "$ref": "#/definitions/NewTabAction" },
{ "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SplitPaneAction" }
{ "$ref": "#/definitions/SplitPaneAction" },
{ "type": "null" }
]
},
"keys": {
@@ -283,6 +277,11 @@
"description": "When set to true, a selection is immediately copied to your clipboard upon creation. When set to false, the selection persists and awaits further action.",
"type": "boolean"
},
"copyFormatting": {
"default": false,
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard.",
"type": "boolean"
},
"defaultProfile": {
"$ref": "#/definitions/ProfileGuid",
"description": "Sets the default profile. Opens by clicking the '+' icon or typing the key binding assigned to 'newTab'. The guid of the desired default profile is used as the value."
@@ -328,7 +327,7 @@
},
"type": "array"
},
"requestedTheme": {
"theme": {
"default": "system",
"description": "Sets the theme of the application.",
"enum": [
@@ -407,7 +406,7 @@
},
"backgroundImage": {
"description": "Sets the file location of the Image to draw over the window background.",
"type": "string"
"type": ["string", "null"]
},
"backgroundImageAlignment": {
"default": "center",
@@ -561,7 +560,7 @@
"description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 25-100.",
"maximum": 100,
"minimum": 25,
"type": "integer"
"type": ["integer","null"]
},
"cursorShape": {
"default": "bar",
@@ -613,7 +612,7 @@
},
"icon": {
"description": "Image file location of the icon used in the profile. Displays within the tab and the dropdown menu.",
"type": "string"
"type": ["string", "null"]
},
"name": {
"description": "Name of the profile. Displays in the dropdown menu.",
@@ -646,7 +645,7 @@
},
"source": {
"description": "Stores the name of the profile generator that originated this profile.",
"type": "string"
"type": ["string", "null"]
},
"startingDirectory": {
"description": "The directory the shell starts in when it is loaded.",
@@ -658,7 +657,7 @@
},
"tabTitle": {
"description": "If set, will replace the name as the title to pass to the shell on startup. Some shells (like bash) may choose to ignore this initial value, while others (cmd, powershell) may use this value over the lifetime of the application.",
"type": "string"
"type": ["string", "null"]
},
"useAcrylic": {
"default": false,
@@ -789,37 +788,15 @@
"type": "array"
}
},
"oneOf": [
"allOf": [
{ "$ref": "#/definitions/Globals" },
{
"allOf": [
{ "$ref": "#/definitions/Globals" },
{
"additionalItems": true,
"properties": {
"profiles": {
"oneOf": [
{ "$ref": "#/definitions/ProfileList" },
{ "$ref": "#/definitions/ProfilesObject" }
]
},
"schemes": { "$ref": "#/definitions/SchemeList" }
},
"required": [
"profiles",
"schemes",
"defaultProfile"
]
}
]
},
{
"additionalItems": false,
"additionalItems": true,
"properties": {
"globals": { "$ref": "#/definitions/Globals" },
"profiles": {
"oneOf": [
{ "$ref": "#/definitions/ProfileList" },
{ "$ref": "#/definitions/ProfilesObject" }
{ "$ref": "#/definitions/ProfileList" },
{ "$ref": "#/definitions/ProfilesObject" }
]
},
"schemes": { "$ref": "#/definitions/SchemeList" }
@@ -827,7 +804,7 @@
"required": [
"profiles",
"schemes",
"globals"
"defaultProfile"
]
}
]

View File

@@ -0,0 +1,88 @@
---
author: Kayla Cinnamon @cinnamon-msft
created on: 2020-04-01
last updated: 2020-04-07
issue id: #4191
---
# Formatted Copy
## Abstract
When copying text, the Terminal should provide the option of including formatting. Not all apps that receive text allow for picking which format you want when pasting. The default should be to only copy plain text, based on the response from this poll on Twitter.
![Twitter poll](twitter-poll.png)
## Solution Proposals
A proposal for the right click behavior as well as two user settings proposals are described below. The conclusion the team arrived at is at the bottom under the [Conclusions section](#conclusions).
1. [Settings option 1 - global setting](#settings-option-1---global-setting)
2. [Settings option 2 - key binding argument](#settings-option-2---key-binding-argument)
3. [Right click behavior](#right-click-behavior)
### Settings option 1 - global setting
We could have a global setting that when enabled, would copy formatting to the clipboard on all copy operations.
### Settings option 2 - key binding argument
We could add an argument to the `copy` key binding argument to allow for formatted copying when the user chooses to do so.
### Right click behavior
By default, right clicking to copy would only copy the plain text.
## UI/UX Design
### Settings option 1 - global setting
a. The user could list which kinds of formats they want included when they copy. When right clicking, they would copy with these formats.
`"copyFormats": ["html","rtf","plain"]`
b. We could also just combine html and rtf into a single boolean. Users would either get plain text only (`false`) or all formatting (`true`) onto their clipboard. If this is set to `true`, the default right click behavior is reversed: right clicking copies the formatting.
`"copyFormatting": true`
### Settings option 2 - key binding argument
a. Just like the `trimWhitespace` argument you can add to the `copy` key binding, we could add one for text formatting. This would not change the right click behavior.
`{"command": {"action": "copy", "keepFormatting": true}, "keys": "ctrl+a"}`
b. We could also split out the html and rtf formats. The right click behavior would still stay as default.
`{"command": {"action": "copy", "formats": ["html","rtf","plain"]}, "keys": "ctrl+a"}`
## Capabilities
### Accessibility
This shouldn't affect accessibility.
### Security
This does not affect security.
### Reliability
This does not affect reliability.
### Compatibility
This breaks the existing behavior of always copying the formatting. The justification for breaking this default behavior is in response to the community saying the default should be plain text only.
### Performance, Power, and Efficiency
## Potential Issues
One possible issue is that discovering how to copy the formatting might be difficult to find. We could mitigate this by adding it into the settings.json file and commenting it out.
## Conclusions
The team has decided to have plain text as the default copy behavior and to enable formatted copying with a global setting that accepts a boolean value (settings option 1 - global setting, option b). In the future, we can modify this setting to also accept an array, so the user can specify which formats they would like to copy. Additionally, a key binding can be added to allow for greater flexibility.
## Future considerations
We could always add an additional option if people want more flexibility. For example, if we ship a global setting now, we could ship a key binding later that lets you choose how you want to copy, and vice versa. Additionally, we can add functionality to the global setting that allows for specific formats or styles to be copied.

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -507,7 +507,7 @@ runtimeclass TerminalParameters {
* [ ] Add a `ShortcutAction` for `FocusPane`, which accepts a single parameter
`index`.
- We'll need to track each `Pane`'s ID as `Pane`s are created, so that we can
quickly switch to the i'th `Pane`.
quickly switch to the nth `Pane`.
- This is in order to support the `-t,--target` parameter of `split-pane`.
## Capabilities

View File

@@ -2,7 +2,7 @@
This doc will hopefully provide a useful guide for adding profiles for common
third-party tools to your
[profiles.json](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md)
[settings.json](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md)
file.
All of these profiles are provided _without_ their `guid` set. If you'd like to

View File

@@ -1,15 +1,15 @@
# Editing Windows Terminal JSON Settings
One way (currently the only way) to configure Windows Terminal is by editing the
`profiles.json` settings file. At the time of writing you can open the settings
`settings.json` settings file. At the time of writing you can open the settings
file in your default editor by selecting `Settings` from the WT pull down menu.
The settings are stored in the file `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\profiles.json`.
The settings are stored in the file `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json`.
As of [#2515](https://github.com/microsoft/terminal/pull/2515), the settings are
split into _two_ files: a hardcoded `defaults.json`, and `profiles.json`, which
split into _two_ files: a hardcoded `defaults.json`, and `settings.json`, which
contains the user settings. Users should only be concerned with the contents of
the `profiles.json`, which contains their customizations. The `defaults.json`
the `settings.json`, which contains their customizations. The `defaults.json`
file is only provided as a reference of what the default settings are. For more
details on how these two files work, see [Settings
Layering](#settings-layering). To view the default settings file, click on the
@@ -164,7 +164,7 @@ The values for background image stretch mode are documented [here](https://docs.
### Hiding a profile
If you want to remove a profile from the list of profiles in the new tab
dropdown, but keep the profile around in your `profiles.json` file, you can add
dropdown, but keep the profile around in your `settings.json` file, you can add
the property `"hidden": true` to the profile's json. This can also be used to
remove the default `cmd` and PowerShell profiles, if the user does not wish to
see them.
@@ -198,10 +198,10 @@ The runtime settings are actually constructed from _three_ sources:
profiles for both Windows PowerShell and Command Prompt (`cmd.exe`).
* Dynamic Profiles, which are generated at runtime. These include Powershell
Core, the Azure Cloud Shell connector, and profiles for and WSL distros.
* The user settings from `profiles.json`.
* The user settings from `settings.json`.
Settings from each of these sources are "layered" upon the settings from
previous sources. In this manner, the user settings in `profiles.json` can
previous sources. In this manner, the user settings in `settings.json` can
contain _only the changes from the default settings_. For example, if a user
would like to only change the color scheme of the default `cmd` profile to
"Solarized Dark", you could change your cmd profile to the following:
@@ -220,7 +220,7 @@ with that GUID will all be treated as the same object. Any changes in that
profile will overwrite those from the defaults.
Similarly, you can overwrite settings from a color scheme by defining a color
scheme in `profiles.json` with the same name as a default color scheme.
scheme in `settings.json` with the same name as a default color scheme.
If you'd like to unbind a keystroke that's bound to an action in the default
keybindings, you can set the `"command"` to `"unbound"` or `null`. This will
@@ -230,9 +230,9 @@ performing the default action.
### Dynamic Profiles
When dynamic profiles are created at runtime, they'll be added to the
`profiles.json` file. You can identify these profiles by the presence of a
`settings.json` file. You can identify these profiles by the presence of a
`"source"` property. These profiles are tied to their source - if you uninstall
a linux distro, then the profile will remain in your `profiles.json` file, but
a linux distro, then the profile will remain in your `settings.json` file, but
the profile will be hidden.
The Windows Terminal uses the `guid` property of these dynamically-generated
@@ -246,7 +246,7 @@ like to hide all the WSL profiles, you could add the following setting:
```json
"disabledProfileSources": ["Microsoft.Terminal.WSL"],
"disabledProfileSources": ["Windows.Terminal.WSL"],
...
```
@@ -371,7 +371,7 @@ In the above settings, the `"fontFace"` in the `cmd.exe` profile overrides the
1. Download the [Debian JPG logo](https://www.debian.org/logos/openlogo-100.jpg)
2. Put the image in the
`$env:LocalAppData\Packages\Microsoft.WindowsTerminal_<randomString>\LocalState\`
directory (same directory as your `profiles.json` file).
directory (same directory as your `settings.json` file).
__NOTE__: You can put the image anywhere you like, the above suggestion happens to be convenient.
3. Open your WT json properties file.
@@ -392,7 +392,7 @@ Notes:
1. You will need to experiment with different color settings
and schemes to make your terminal text visible on top of your image
2. If you store the image in the UWP directory (the same directory as your profiles.json file),
2. If you store the image in the UWP directory (the same directory as your settings.json file),
then you should use the URI style path name given in the above example.
More information about UWP URI schemes [here](https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes).
3. Instead of using a UWP URI you can use a:
@@ -414,7 +414,7 @@ following objects into your `globals.keybindings` array:
{ "command": "paste", "keys": ["ctrl+shift+v"] }
```
> 👉 **Note**: you can also add a keybinding for the `copyTextWithoutNewlines` command. This removes newlines as the text is copied to your clipboard.
> 👉 **Note**: you can also add a keybinding for the `copy` command with the argument `"trimWhitespace": true`. This removes newlines as the text is copied to your clipboard.
This will add copy and paste on <kbd>ctrl+shift+c</kbd>
and <kbd>ctrl+shift+v</kbd> respectively.

View File

@@ -65,7 +65,7 @@ Not currently supported "out of the box". See issue [#1060](https://github.com/m
## Configuring Windows Terminal
All Windows Terminal settings are currently managed using the `profiles.json` file, located within `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState`.
All Windows Terminal settings are currently managed using the `settings.json` file, located within `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState`.
To open the settings file from Windows Terminal:
@@ -73,7 +73,7 @@ To open the settings file from Windows Terminal:
2. From the dropdown list, click `Settings`. You can also use a shortcut: <kbd>Ctrl</kbd>+<kbd>,</kbd>.
3. Your default `json` editor will open the settings file.
For an introduction to the various settings, see [Using Json Settings](UsingJsonSettings.md). The list of valid settings can be found in the [profiles.json documentation](../cascadia/SettingsSchema.md) section.
For an introduction to the various settings, see [Using Json Settings](UsingJsonSettings.md). The list of valid settings can be found in the [settings.json documentation](../cascadia/SettingsSchema.md) section.
## Tips and Tricks

View File

@@ -590,7 +590,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
@@ -650,7 +650,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
@@ -704,7 +704,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());

View File

@@ -170,21 +170,18 @@ namespace TerminalAppLocalTests
{
const std::string bindings0String{ R"([
{ "command": "copy", "keys": ["ctrl+c"] },
{ "command": "copyTextWithoutNewlines", "keys": ["alt+c"] },
{ "command": { "action": "copy", "trimWhitespace": false }, "keys": ["ctrl+shift+c"] },
{ "command": { "action": "copy", "trimWhitespace": true }, "keys": ["alt+shift+c"] },
{ "command": { "action": "copy", "singleLine": false }, "keys": ["ctrl+shift+c"] },
{ "command": { "action": "copy", "singleLine": true }, "keys": ["alt+shift+c"] },
{ "command": "newTab", "keys": ["ctrl+t"] },
{ "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+t"] },
{ "command": "newTabProfile0", "keys": ["alt+shift+t"] },
{ "command": { "action": "newTab", "index": 11 }, "keys": ["ctrl+shift+y"] },
{ "command": "newTabProfile8", "keys": ["alt+shift+y"] },
{ "command": { "action": "copy", "madeUpBool": true }, "keys": ["ctrl+b"] },
{ "command": { "action": "copy" }, "keys": ["ctrl+shift+b"] },
{ "command": "increaseFontSize", "keys": ["ctrl+f"] },
{ "command": "decreaseFontSize", "keys": ["ctrl+g"] }
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": ["ctrl+f"] },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": ["ctrl+g"] }
])" };
@@ -194,28 +191,17 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(13u, appKeyBindings->_keyShortcuts.size());
VERIFY_ARE_EQUAL(10u, appKeyBindings->_keyShortcuts.size());
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` without args parses as Copy(TrimWhitespace=true)"));
L"Verify that `copy` without args parses as Copy(SingleLine=false)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `copyTextWithoutNewlines` parses as Copy(TrimWhitespace=false)"));
KeyChord kc{ false, true, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.TrimWhitespace());
VERIFY_IS_FALSE(realArgs.SingleLine());
}
{
@@ -226,7 +212,7 @@ namespace TerminalAppLocalTests
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.TrimWhitespace());
VERIFY_IS_FALSE(realArgs.SingleLine());
}
{
@@ -237,7 +223,7 @@ namespace TerminalAppLocalTests
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
VERIFY_IS_TRUE(realArgs.SingleLine());
}
{
@@ -265,19 +251,6 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
VERIFY_ARE_EQUAL(0, realArgs.TerminalArgs().ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTabProfile0` parses as NewTab(Index=0)"));
KeyChord kc{ false, true, true, static_cast<int32_t>('T') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTabProfile0, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
VERIFY_ARE_EQUAL(0, realArgs.TerminalArgs().ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` with an index greater than the legacy "
@@ -292,19 +265,6 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
VERIFY_ARE_EQUAL(11, realArgs.TerminalArgs().ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTabProfile8` parses as NewTab(Index=8)"));
KeyChord kc{ false, true, true, static_cast<int32_t>('Y') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTabProfile8, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
VERIFY_ARE_EQUAL(8, realArgs.TerminalArgs().ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
@@ -315,7 +275,7 @@ namespace TerminalAppLocalTests
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
VERIFY_IS_FALSE(realArgs.SingleLine());
}
{
@@ -327,15 +287,15 @@ namespace TerminalAppLocalTests
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
VERIFY_IS_FALSE(realArgs.SingleLine());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `increaseFontSize` without args parses as AdjustFontSize(Delta=1)"));
L"Verify that `adjustFontSize` with a positive delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::IncreaseFontSize, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -344,10 +304,10 @@ namespace TerminalAppLocalTests
{
Log::Comment(NoThrowString().Format(
L"Verify that `decreaseFontSize` without args parses as AdjustFontSize(Delta=-1)"));
L"Verify that `adjustFontSize` with a negative delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::DecreaseFontSize, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -358,8 +318,6 @@ namespace TerminalAppLocalTests
void KeyBindingsTests::TestSplitPaneArgs()
{
const std::string bindings0String{ R"([
{ "keys": ["ctrl+a"], "command": "splitVertical" },
{ "keys": ["ctrl+b"], "command": "splitHorizontal" },
{ "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": null } },
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } },
@@ -375,26 +333,8 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(9u, appKeyBindings->_keyShortcuts.size());
VERIFY_ARE_EQUAL(7u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitVertical, 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());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitHorizontal, 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());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
@@ -402,7 +342,7 @@ namespace TerminalAppLocalTests
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::None, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
@@ -429,7 +369,7 @@ namespace TerminalAppLocalTests
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::None, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
@@ -438,7 +378,7 @@ namespace TerminalAppLocalTests
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::None, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
@@ -456,7 +396,7 @@ namespace TerminalAppLocalTests
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::None, realArgs.SplitStyle());
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
}
@@ -480,7 +420,7 @@ namespace TerminalAppLocalTests
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
VERIFY_IS_FALSE(realArgs.SingleLine());
}
}
}

View File

@@ -56,8 +56,6 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestInvalidColorSchemeName);
TEST_METHOD(TestHelperFunctions);
TEST_METHOD(TestLayerGlobalsOnRoot);
TEST_METHOD(TestProfileIconWithEnvVar);
TEST_METHOD(TestProfileBackgroundImageWithEnvVar);
@@ -160,9 +158,7 @@ namespace TerminalAppLocalTests
{
const std::string goodProfiles{ R"(
{
"globals": {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
},
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
@@ -177,9 +173,7 @@ namespace TerminalAppLocalTests
const std::string badProfiles{ R"(
{
"globals": {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
},
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
@@ -194,9 +188,7 @@ namespace TerminalAppLocalTests
const std::string noDefaultAtAll{ R"(
{
"globals": {
"alwaysShowTabs": true
},
"alwaysShowTabs": true,
"profiles": [
{
"name" : "profile0",
@@ -388,9 +380,7 @@ namespace TerminalAppLocalTests
{
const std::string badProfiles{ R"(
{
"globals": {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
},
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
@@ -438,21 +428,17 @@ namespace TerminalAppLocalTests
{
const std::string settings0String{ R"(
{
"globals": {
"alwaysShowTabs": true,
"initialCols" : 120,
"initialRows" : 30,
"rowsToScroll" : 4
}
"alwaysShowTabs": true,
"initialCols" : 120,
"initialRows" : 30,
"rowsToScroll" : 4
})" };
const std::string settings1String{ R"(
{
"globals": {
"showTabsInTitlebar": false,
"initialCols" : 240,
"initialRows" : 60,
"rowsToScroll" : 8
}
"showTabsInTitlebar": false,
"initialCols" : 240,
"initialRows" : 60,
"rowsToScroll" : 8
})" };
const auto settings0Json = VerifyParseSucceeded(settings0String);
const auto settings1Json = VerifyParseSucceeded(settings1String);
@@ -1348,114 +1334,6 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(name2, prof2->GetName());
}
void SettingsTests::TestLayerGlobalsOnRoot()
{
// Test for microsoft/terminal#2906. We added the ability for the root
// to be used as the globals object in #2515. However, if you have a
// globals object, then the settings in the root would get ignored.
// This test ensures that settings from a child "globals" element
// _layer_ on top of root properties, and they don't cause the root
// properties to be totally ignored.
const std::string settings0String{ R"(
{
"globals": {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"initialRows": 123
}
})" };
const std::string settings1String{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"initialRows": 234
})" };
const std::string settings2String{ R"(
{
"defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"initialRows": 345,
"globals": {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
// initialRows should not be cleared here
}
})" };
const std::string settings3String{ R"(
{
"defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"globals": {
"initialRows": 456
// defaultProfile should not be cleared here
}
})" };
const std::string settings4String{ R"(
{
"defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"globals": {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
},
"defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}"
})" };
const std::string settings5String{ R"(
{
"globals": {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
},
"defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"globals": {
"defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}"
}
})" };
VerifyParseSucceeded(settings0String);
VerifyParseSucceeded(settings1String);
VerifyParseSucceeded(settings2String);
VerifyParseSucceeded(settings3String);
VerifyParseSucceeded(settings4String);
VerifyParseSucceeded(settings5String);
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
{
CascadiaSettings settings;
settings._ParseJsonString(settings0String, false);
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(guid1, settings._globals._defaultProfile);
VERIFY_ARE_EQUAL(123, settings._globals._initialRows);
}
{
CascadiaSettings settings;
settings._ParseJsonString(settings1String, false);
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(guid1, settings._globals._defaultProfile);
VERIFY_ARE_EQUAL(234, settings._globals._initialRows);
}
{
CascadiaSettings settings;
settings._ParseJsonString(settings2String, false);
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(guid1, settings._globals._defaultProfile);
VERIFY_ARE_EQUAL(345, settings._globals._initialRows);
}
{
CascadiaSettings settings;
settings._ParseJsonString(settings3String, false);
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(guid2, settings._globals._defaultProfile);
VERIFY_ARE_EQUAL(456, settings._globals._initialRows);
}
{
CascadiaSettings settings;
settings._ParseJsonString(settings4String, false);
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(guid1, settings._globals._defaultProfile);
}
{
CascadiaSettings settings;
settings._ParseJsonString(settings5String, false);
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(guid3, settings._globals._defaultProfile);
}
}
void SettingsTests::TestProfileIconWithEnvVar()
{
const auto expectedPath = wil::ExpandEnvironmentStringsW<std::wstring>(L"%WINDIR%\\System32\\x_80.png");
@@ -2311,9 +2189,7 @@ namespace TerminalAppLocalTests
{
const std::string badSettings{ R"(
{
"globals": {
"defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
},
"defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",

View File

@@ -277,219 +277,227 @@ namespace TerminalAppLocalTests
void TabTests::TryDuplicateBadTab()
{
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _DuplicateTabViewItem on tab 1
// * No new tab should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
Log::Result(WEX::Logging::TestResults::Skipped);
return;
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// // * Create a tab with a profile with GUID 1
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
// // * Try calling _DuplicateTabViewItem on tab 1
// // * No new tab should be created (and more importantly, the app should not crash)
// //
// // Created to test GH#2455
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson0{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile0",
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "historySize": 1
// },
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson0);
auto settings0 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings0);
settings0->_ParseJsonString(settingsJson0, false);
settings0->LayerJson(settings0->_userSettings);
settings0->_ValidateSettings();
// const std::string settingsJson1{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson1);
auto settings1 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings1);
settings1->_ParseJsonString(settingsJson1, false);
settings1->LayerJson(settings1->_userSettings);
settings1->_ValidateSettings();
// VerifyParseSucceeded(settingsJson0);
// auto settings0 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings0);
// settings0->_ParseJsonString(settingsJson0, false);
// settings0->LayerJson(settings0->_userSettings);
// settings0->_ValidateSettings();
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
// VerifyParseSucceeded(settingsJson1);
// auto settings1 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings1);
// settings1->_ParseJsonString(settingsJson1, false);
// settings1->LayerJson(settings1->_userSettings);
// settings1->_ValidateSettings();
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
// const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
// const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
// const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// // This is super wacky, but we can't just initialize the
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// // during TerminalPage::Create() below.
// //
// // Instead, create the winrt object, then get a com_ptr to the
// // implementation _from_ the winrt object. This seems to work, even if
// // it's weird.
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
// _initializeTerminalPage(page, settings0);
Log::Comment(L"Duplicate the first tab");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// auto result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = settings1;
});
VERIFY_SUCCEEDED(result);
// Log::Comment(L"Duplicate the first tab");
// result = RunOnUIThread([&page]() {
// page->_DuplicateTabViewItem();
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(L"Duplicate the tab, and don't crash");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(
// L"Change the settings of the TerminalPage so the first profile is "
// L"no longer in the list of profiles"));
// result = RunOnUIThread([&page, settings1]() {
// page->_settings = settings1;
// });
// VERIFY_SUCCEEDED(result);
// Log::Comment(L"Duplicate the tab, and don't crash");
// result = RunOnUIThread([&page]() {
// page->_DuplicateTabViewItem();
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
// });
// VERIFY_SUCCEEDED(result);
}
void TabTests::TryDuplicateBadPane()
{
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _SplitPane(Duplicate) on tab 1
// * No new pane should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
Log::Result(WEX::Logging::TestResults::Skipped);
return;
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// // * Create a tab with a profile with GUID 1
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
// // * Try calling _SplitPane(Duplicate) on tab 1
// // * No new pane should be created (and more importantly, the app should not crash)
// //
// // Created to test GH#2455
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson0{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile0",
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "historySize": 1
// },
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson0);
auto settings0 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings0);
settings0->_ParseJsonString(settingsJson0, false);
settings0->LayerJson(settings0->_userSettings);
settings0->_ValidateSettings();
// const std::string settingsJson1{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson1);
auto settings1 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings1);
settings1->_ParseJsonString(settingsJson1, false);
settings1->LayerJson(settings1->_userSettings);
settings1->_ValidateSettings();
// VerifyParseSucceeded(settingsJson0);
// auto settings0 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings0);
// settings0->_ParseJsonString(settingsJson0, false);
// settings0->LayerJson(settings0->_userSettings);
// settings0->_ValidateSettings();
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
// VerifyParseSucceeded(settingsJson1);
// auto settings1 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings1);
// settings1->_ParseJsonString(settingsJson1, false);
// settings1->LayerJson(settings1->_userSettings);
// settings1->_ValidateSettings();
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
// const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
// const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
// const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// // This is super wacky, but we can't just initialize the
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// // during TerminalPage::Create() below.
// //
// // Instead, create the winrt object, then get a com_ptr to the
// // implementation _from_ the winrt object. This seems to work, even if
// // it's weird.
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
// _initializeTerminalPage(page, settings0);
result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
// auto result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
// result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
// });
// VERIFY_SUCCEEDED(result);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
// result = RunOnUIThread([&page]() {
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = settings1;
});
VERIFY_SUCCEEDED(result);
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
// Log::Comment(NoThrowString().Format(
// L"Change the settings of the TerminalPage so the first profile is "
// L"no longer in the list of profiles"));
// result = RunOnUIThread([&page, settings1]() {
// page->_settings = settings1;
// });
// VERIFY_SUCCEEDED(result);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2,
tab->_GetLeafPaneCount(),
L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
// result = RunOnUIThread([&page]() {
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
auto cleanup = wil::scope_exit([] {
auto result = RunOnUIThread([]() {
// There's something causing us to crash north of
// TSFInputControl::NotifyEnter, or LayoutRequested. It's very
// unclear what that issue is. Since these tests don't run in
// CI, simply log a message so that the dev running these tests
// knows it's expected.
Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
});
VERIFY_SUCCEEDED(result);
});
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(2,
// tab->_GetLeafPaneCount(),
// L"We should gracefully do nothing here - the profile no longer exists.");
// });
// VERIFY_SUCCEEDED(result);
// auto cleanup = wil::scope_exit([] {
// auto result = RunOnUIThread([]() {
// // There's something causing us to crash north of
// // TSFInputControl::NotifyEnter, or LayoutRequested. It's very
// // unclear what that issue is. Since these tests don't run in
// // CI, simply log a message so that the dev running these tests
// // knows it's expected.
// Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
// });
// VERIFY_SUCCEEDED(result);
// });
}
}

View File

@@ -427,10 +427,8 @@ const wchar_t* _stdcall TerminalGetSelection(void* terminal)
return returnText.release();
}
void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam)
static ControlKeyStates getControlKeyState() noexcept
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto scanCode = MapVirtualKeyW((UINT)wParam, MAPVK_VK_TO_VSC);
struct KeyModifier
{
int vkey;
@@ -458,10 +456,17 @@ void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam)
}
}
publicTerminal->_terminal->SendKeyEvent((WORD)wParam, (WORD)scanCode, flags);
return flags;
}
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch)
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto flags = getControlKeyState();
publicTerminal->_terminal->SendKeyEvent(vkey, scanCode, flags);
}
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode)
{
if (ch == '\t')
{
@@ -469,7 +474,8 @@ void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch)
}
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
publicTerminal->_terminal->SendCharEvent(ch);
const auto flags = getControlKeyState();
publicTerminal->_terminal->SendCharEvent(ch, scanCode, flags);
}
void _stdcall DestroyTerminal(void* terminal)

View File

@@ -34,8 +34,8 @@ __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch);
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
};
@@ -82,8 +82,8 @@ private:
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
friend void _stdcall TerminalBlinkCursor(void* terminal);
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);

View File

@@ -93,9 +93,9 @@ namespace winrt::TerminalApp::implementation
struct CopyTextArgs : public CopyTextArgsT<CopyTextArgs>
{
CopyTextArgs() = default;
GETSET_PROPERTY(bool, TrimWhitespace, true);
GETSET_PROPERTY(bool, SingleLine, false);
static constexpr std::string_view TrimWhitespaceKey{ "trimWhitespace" };
static constexpr std::string_view SingleLineKey{ "singleLine" };
public:
bool Equals(const IActionArgs& other)
@@ -103,7 +103,7 @@ namespace winrt::TerminalApp::implementation
auto otherAsUs = other.try_as<CopyTextArgs>();
if (otherAsUs)
{
return otherAsUs->_TrimWhitespace == _TrimWhitespace;
return otherAsUs->_SingleLine == _SingleLine;
}
return false;
};
@@ -111,9 +111,9 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<CopyTextArgs>();
if (auto trimWhitespace{ json[JsonKey(TrimWhitespaceKey)] })
if (auto singleLine{ json[JsonKey(SingleLineKey)] })
{
args->_TrimWhitespace = trimWhitespace.asBool();
args->_SingleLine = singleLine.asBool();
}
return { *args, {} };
}
@@ -328,7 +328,7 @@ namespace winrt::TerminalApp::implementation
return TerminalApp::SplitState::Automatic;
}
// default behavior for invalid data
return TerminalApp::SplitState::None;
return TerminalApp::SplitState::Automatic;
};
// Possible SplitType values
@@ -345,7 +345,7 @@ namespace winrt::TerminalApp::implementation
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
{
SplitPaneArgs() = default;
GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::None);
GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::Automatic);
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
GETSET_PROPERTY(winrt::TerminalApp::SplitType, SplitMode, winrt::TerminalApp::SplitType::Manual);

View File

@@ -56,7 +56,7 @@ namespace TerminalApp
[default_interface] runtimeclass CopyTextArgs : IActionArgs
{
Boolean TrimWhitespace { get; };
Boolean SingleLine { get; };
};
[default_interface] runtimeclass NewTabArgs : IActionArgs

View File

@@ -198,7 +198,7 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::CopyTextArgs>())
{
const auto handled = _CopyText(realArgs.TrimWhitespace());
const auto handled = _CopyText(realArgs.SingleLine());
args.Handled(handled);
}
}

View File

@@ -217,7 +217,7 @@ void AppCommandlineArgs::_buildSplitPaneParser()
args->SplitStyle(SplitState::Automatic);
// Make sure to use the `Option`s here to check if they were set -
// _getNewTerminalArgs might reset them while parsing a commandline
if ((*_horizontalOption || *_verticalOption) && (_splitHorizontal))
if ((*_horizontalOption || *_verticalOption))
{
if (_splitHorizontal)
{
@@ -225,7 +225,7 @@ void AppCommandlineArgs::_buildSplitPaneParser()
}
else if (_splitVertical)
{
args->SplitStyle(SplitState::Horizontal);
args->SplitStyle(SplitState::Vertical);
}
}

View File

@@ -25,20 +25,10 @@ static constexpr std::string_view ActionKey{ "action" };
static constexpr std::string_view UnboundKey{ "unbound" };
static constexpr std::string_view CopyTextKey{ "copy" };
static constexpr std::string_view CopyTextWithoutNewlinesKey{ "copyTextWithoutNewlines" }; // Legacy
static constexpr std::string_view PasteTextKey{ "paste" };
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
static constexpr std::string_view DuplicateTabKey{ "duplicateTab" };
static constexpr std::string_view NewTabKey{ "newTab" };
static constexpr std::string_view NewTabWithProfile0Key{ "newTabProfile0" }; // Legacy
static constexpr std::string_view NewTabWithProfile1Key{ "newTabProfile1" }; // Legacy
static constexpr std::string_view NewTabWithProfile2Key{ "newTabProfile2" }; // Legacy
static constexpr std::string_view NewTabWithProfile3Key{ "newTabProfile3" }; // Legacy
static constexpr std::string_view NewTabWithProfile4Key{ "newTabProfile4" }; // Legacy
static constexpr std::string_view NewTabWithProfile5Key{ "newTabProfile5" }; // Legacy
static constexpr std::string_view NewTabWithProfile6Key{ "newTabProfile6" }; // Legacy
static constexpr std::string_view NewTabWithProfile7Key{ "newTabProfile7" }; // Legacy
static constexpr std::string_view NewTabWithProfile8Key{ "newTabProfile8" }; // Legacy
static constexpr std::string_view NewWindowKey{ "newWindow" };
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
static constexpr std::string_view CloseTabKey{ "closeTab" };
@@ -46,37 +36,17 @@ static constexpr std::string_view ClosePaneKey{ "closePane" };
static constexpr std::string_view SwitchtoTabKey{ "switchToTab" };
static constexpr std::string_view NextTabKey{ "nextTab" };
static constexpr std::string_view PrevTabKey{ "prevTab" };
static constexpr std::string_view IncreaseFontSizeKey{ "increaseFontSize" };
static constexpr std::string_view DecreaseFontSizeKey{ "decreaseFontSize" };
static constexpr std::string_view AdjustFontSizeKey{ "adjustFontSize" };
static constexpr std::string_view ResetFontSizeKey{ "resetFontSize" };
static constexpr std::string_view ScrollupKey{ "scrollUp" };
static constexpr std::string_view ScrolldownKey{ "scrollDown" };
static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" };
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
static constexpr std::string_view SwitchToTab0Key{ "switchToTab0" }; // Legacy
static constexpr std::string_view SwitchToTab1Key{ "switchToTab1" }; // Legacy
static constexpr std::string_view SwitchToTab2Key{ "switchToTab2" }; // Legacy
static constexpr std::string_view SwitchToTab3Key{ "switchToTab3" }; // Legacy
static constexpr std::string_view SwitchToTab4Key{ "switchToTab4" }; // Legacy
static constexpr std::string_view SwitchToTab5Key{ "switchToTab5" }; // Legacy
static constexpr std::string_view SwitchToTab6Key{ "switchToTab6" }; // Legacy
static constexpr std::string_view SwitchToTab7Key{ "switchToTab7" }; // Legacy
static constexpr std::string_view SwitchToTab8Key{ "switchToTab8" }; // Legacy
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // Legacy
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings
static constexpr std::string_view SplitPaneKey{ "splitPane" };
static constexpr std::string_view SplitHorizontalKey{ "splitHorizontal" }; // Legacy
static constexpr std::string_view SplitVerticalKey{ "splitVertical" }; // Legacy
static constexpr std::string_view ResizePaneKey{ "resizePane" };
static constexpr std::string_view ResizePaneLeftKey{ "resizePaneLeft" }; // Legacy
static constexpr std::string_view ResizePaneRightKey{ "resizePaneRight" }; // Legacy
static constexpr std::string_view ResizePaneUpKey{ "resizePaneUp" }; // Legacy
static constexpr std::string_view ResizePaneDownKey{ "resizePaneDown" }; // Legacy
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
static constexpr std::string_view MoveFocusLeftKey{ "moveFocusLeft" }; // Legacy
static constexpr std::string_view MoveFocusRightKey{ "moveFocusRight" }; // Legacy
static constexpr std::string_view MoveFocusUpKey{ "moveFocusUp" }; // Legacy
static constexpr std::string_view MoveFocusDownKey{ "moveFocusDown" }; // Legacy
static constexpr std::string_view FindKey{ "find" };
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
@@ -90,55 +60,25 @@ static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
// about here.
static const std::map<std::string_view, ShortcutAction, std::less<>> commandNames{
{ CopyTextKey, ShortcutAction::CopyText },
{ CopyTextWithoutNewlinesKey, ShortcutAction::CopyTextWithoutNewlines },
{ PasteTextKey, ShortcutAction::PasteText },
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
{ DuplicateTabKey, ShortcutAction::DuplicateTab },
{ NewTabKey, ShortcutAction::NewTab },
{ NewTabWithProfile0Key, ShortcutAction::NewTabProfile0 },
{ NewTabWithProfile1Key, ShortcutAction::NewTabProfile1 },
{ NewTabWithProfile2Key, ShortcutAction::NewTabProfile2 },
{ NewTabWithProfile3Key, ShortcutAction::NewTabProfile3 },
{ NewTabWithProfile4Key, ShortcutAction::NewTabProfile4 },
{ NewTabWithProfile5Key, ShortcutAction::NewTabProfile5 },
{ NewTabWithProfile6Key, ShortcutAction::NewTabProfile6 },
{ NewTabWithProfile7Key, ShortcutAction::NewTabProfile7 },
{ NewTabWithProfile8Key, ShortcutAction::NewTabProfile8 },
{ NewWindowKey, ShortcutAction::NewWindow },
{ CloseWindowKey, ShortcutAction::CloseWindow },
{ CloseTabKey, ShortcutAction::CloseTab },
{ ClosePaneKey, ShortcutAction::ClosePane },
{ NextTabKey, ShortcutAction::NextTab },
{ PrevTabKey, ShortcutAction::PrevTab },
{ IncreaseFontSizeKey, ShortcutAction::IncreaseFontSize },
{ DecreaseFontSizeKey, ShortcutAction::DecreaseFontSize },
{ AdjustFontSizeKey, ShortcutAction::AdjustFontSize },
{ ResetFontSizeKey, ShortcutAction::ResetFontSize },
{ ScrollupKey, ShortcutAction::ScrollUp },
{ ScrolldownKey, ShortcutAction::ScrollDown },
{ ScrolluppageKey, ShortcutAction::ScrollUpPage },
{ ScrolldownpageKey, ShortcutAction::ScrollDownPage },
{ SwitchToTabKey, ShortcutAction::SwitchToTab },
{ SwitchToTab0Key, ShortcutAction::SwitchToTab0 },
{ SwitchToTab1Key, ShortcutAction::SwitchToTab1 },
{ SwitchToTab2Key, ShortcutAction::SwitchToTab2 },
{ SwitchToTab3Key, ShortcutAction::SwitchToTab3 },
{ SwitchToTab4Key, ShortcutAction::SwitchToTab4 },
{ SwitchToTab5Key, ShortcutAction::SwitchToTab5 },
{ SwitchToTab6Key, ShortcutAction::SwitchToTab6 },
{ SwitchToTab7Key, ShortcutAction::SwitchToTab7 },
{ SwitchToTab8Key, ShortcutAction::SwitchToTab8 },
{ SplitHorizontalKey, ShortcutAction::SplitHorizontal },
{ SplitVerticalKey, ShortcutAction::SplitVertical },
{ ResizePaneKey, ShortcutAction::ResizePane },
{ ResizePaneLeftKey, ShortcutAction::ResizePaneLeft },
{ ResizePaneRightKey, ShortcutAction::ResizePaneRight },
{ ResizePaneUpKey, ShortcutAction::ResizePaneUp },
{ ResizePaneDownKey, ShortcutAction::ResizePaneDown },
{ MoveFocusKey, ShortcutAction::MoveFocus },
{ MoveFocusLeftKey, ShortcutAction::MoveFocusLeft },
{ MoveFocusRightKey, ShortcutAction::MoveFocusRight },
{ MoveFocusUpKey, ShortcutAction::MoveFocusUp },
{ MoveFocusDownKey, ShortcutAction::MoveFocusDown },
{ OpenSettingsKey, ShortcutAction::OpenSettings },
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
{ SplitPaneKey, ShortcutAction::SplitPane },
@@ -149,146 +89,6 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
using ParseResult = std::tuple<IActionArgs, std::vector<TerminalApp::SettingsLoadWarnings>>;
using ParseActionFunction = std::function<ParseResult(const Json::Value&)>;
// Function Description:
// - Creates a function that can be used to generate a SplitPaneArgs for the
// legacy Split[SplitState] actions. These actions don't accept args from
// json, instead, they just return a SplitPaneArgs with the style already
// pre-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - style: the split style to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// Split[SplitState] args.
ParseActionFunction LegacyParseSplitPaneArgs(SplitState style)
{
auto pfn = [style](const Json::Value & /*value*/) -> ParseResult {
auto args = winrt::make_self<winrt::TerminalApp::implementation::SplitPaneArgs>();
args->SplitStyle(style);
return { *args, {} };
};
return pfn;
}
// Function Description:
// - Creates a function that can be used to generate a MoveFocusArgs for the
// legacy MoveFocus[Direction] actions. These actions don't accept args from
// json, instead, they just return a MoveFocusArgs with the Direction already
// per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - direction: the direction to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// MoveFocus[Direction] args.
ParseActionFunction LegacyParseMoveFocusArgs(Direction direction)
{
auto pfn = [direction](const Json::Value & /*value*/) -> ParseResult {
auto args = winrt::make_self<winrt::TerminalApp::implementation::MoveFocusArgs>();
args->Direction(direction);
return { *args, {} };
};
return pfn;
}
// Function Description:
// - Creates a function that can be used to generate a ResizePaneArgs for the
// legacy ResizePane[Direction] actions. These actions don't accept args from
// json, instead, they just return a ResizePaneArgs with the Direction already
// per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - direction: the direction to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// ResizePane[Direction] args.
ParseActionFunction LegacyParseResizePaneArgs(Direction direction)
{
auto pfn = [direction](const Json::Value & /*value*/) -> ParseResult {
auto args = winrt::make_self<winrt::TerminalApp::implementation::ResizePaneArgs>();
args->Direction(direction);
return { *args, {} };
};
return pfn;
}
// Function Description:
// - Creates a function that can be used to generate a NewTabWithProfileArgs for
// the legacy NewTabWithProfile[Index] actions. These actions don't accept
// args from json, instead, they just return a NewTabWithProfileArgs with the
// index already per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - index: the profile index to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// NewTabWithProfile[Index] args.
ParseActionFunction LegacyParseNewTabWithProfileArgs(int index)
{
auto pfn = [index](const Json::Value & /*value*/) -> ParseResult {
auto args = winrt::make_self<winrt::TerminalApp::implementation::NewTabArgs>();
auto newTerminalArgs = winrt::make_self<winrt::TerminalApp::implementation::NewTerminalArgs>();
newTerminalArgs->ProfileIndex(index);
args->TerminalArgs(*newTerminalArgs);
return { *args, {} };
};
return pfn;
}
// Function Description:
// - Creates a function that can be used to generate a SwitchToTabArgs for the
// legacy SwitchToTab[Index] actions. These actions don't accept args from
// json, instead, they just return a SwitchToTabArgs with the index already
// per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - index: the tab index to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// SwitchToTab[Index] args.
ParseActionFunction LegacyParseSwitchToTabArgs(int index)
{
auto pfn = [index](const Json::Value & /*value*/) -> ParseResult {
auto args = winrt::make_self<winrt::TerminalApp::implementation::SwitchToTabArgs>();
args->TabIndex(index);
return { *args, {} };
};
return pfn;
}
// Function Description:
// - Used to generate a CopyTextArgs for the legacy CopyTextWithoutNewlines
// action.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - direction: the direction to create the parse function for.
// Return Value:
// - A CopyTextArgs with TrimWhitespace set to true, to emulate "CopyTextWithoutNewlines".
ParseResult LegacyParseCopyTextWithoutNewlinesArgs(const Json::Value& /*json*/)
{
auto args = winrt::make_self<winrt::TerminalApp::implementation::CopyTextArgs>();
args->TrimWhitespace(false);
return { *args, {} };
};
// Function Description:
// - Used to generate a AdjustFontSizeArgs for IncreaseFontSize/DecreaseFontSize
// actions with a delta of 1/-1.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - delta: the font size delta to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into an AdjustFontSizeArgs.
ParseActionFunction LegacyParseAdjustFontSizeArgs(int delta)
{
auto pfn = [delta](const Json::Value & /*value*/) -> ParseResult {
auto args = winrt::make_self<winrt::TerminalApp::implementation::AdjustFontSizeArgs>();
args->Delta(delta);
return { *args, {} };
};
return pfn;
}
// This is a map of ShortcutAction->function<IActionArgs(Json::Value)>. It holds
// a set of deserializer functions that can be used to deserialize a IActionArgs
// from json. Each type of IActionArgs that can accept arbitrary args should be
@@ -296,48 +96,18 @@ ParseActionFunction LegacyParseAdjustFontSizeArgs(int delta)
// value.
static const std::map<ShortcutAction, ParseActionFunction, std::less<>> argParsers{
{ ShortcutAction::CopyText, winrt::TerminalApp::implementation::CopyTextArgs::FromJson },
{ ShortcutAction::CopyTextWithoutNewlines, LegacyParseCopyTextWithoutNewlinesArgs },
{ ShortcutAction::NewTab, winrt::TerminalApp::implementation::NewTabArgs::FromJson },
{ ShortcutAction::NewTabProfile0, LegacyParseNewTabWithProfileArgs(0) },
{ ShortcutAction::NewTabProfile1, LegacyParseNewTabWithProfileArgs(1) },
{ ShortcutAction::NewTabProfile2, LegacyParseNewTabWithProfileArgs(2) },
{ ShortcutAction::NewTabProfile3, LegacyParseNewTabWithProfileArgs(3) },
{ ShortcutAction::NewTabProfile4, LegacyParseNewTabWithProfileArgs(4) },
{ ShortcutAction::NewTabProfile5, LegacyParseNewTabWithProfileArgs(5) },
{ ShortcutAction::NewTabProfile6, LegacyParseNewTabWithProfileArgs(6) },
{ ShortcutAction::NewTabProfile7, LegacyParseNewTabWithProfileArgs(7) },
{ ShortcutAction::NewTabProfile8, LegacyParseNewTabWithProfileArgs(8) },
{ ShortcutAction::SwitchToTab, winrt::TerminalApp::implementation::SwitchToTabArgs::FromJson },
{ ShortcutAction::SwitchToTab0, LegacyParseSwitchToTabArgs(0) },
{ ShortcutAction::SwitchToTab1, LegacyParseSwitchToTabArgs(1) },
{ ShortcutAction::SwitchToTab2, LegacyParseSwitchToTabArgs(2) },
{ ShortcutAction::SwitchToTab3, LegacyParseSwitchToTabArgs(3) },
{ ShortcutAction::SwitchToTab4, LegacyParseSwitchToTabArgs(4) },
{ ShortcutAction::SwitchToTab5, LegacyParseSwitchToTabArgs(5) },
{ ShortcutAction::SwitchToTab6, LegacyParseSwitchToTabArgs(6) },
{ ShortcutAction::SwitchToTab7, LegacyParseSwitchToTabArgs(7) },
{ ShortcutAction::SwitchToTab8, LegacyParseSwitchToTabArgs(8) },
{ ShortcutAction::ResizePane, winrt::TerminalApp::implementation::ResizePaneArgs::FromJson },
{ ShortcutAction::ResizePaneLeft, LegacyParseResizePaneArgs(Direction::Left) },
{ ShortcutAction::ResizePaneRight, LegacyParseResizePaneArgs(Direction::Right) },
{ ShortcutAction::ResizePaneUp, LegacyParseResizePaneArgs(Direction::Up) },
{ ShortcutAction::ResizePaneDown, LegacyParseResizePaneArgs(Direction::Down) },
{ ShortcutAction::MoveFocus, winrt::TerminalApp::implementation::MoveFocusArgs::FromJson },
{ ShortcutAction::MoveFocusLeft, LegacyParseMoveFocusArgs(Direction::Left) },
{ ShortcutAction::MoveFocusRight, LegacyParseMoveFocusArgs(Direction::Right) },
{ ShortcutAction::MoveFocusUp, LegacyParseMoveFocusArgs(Direction::Up) },
{ ShortcutAction::MoveFocusDown, LegacyParseMoveFocusArgs(Direction::Down) },
{ ShortcutAction::DecreaseFontSize, LegacyParseAdjustFontSizeArgs(-1) },
{ ShortcutAction::IncreaseFontSize, LegacyParseAdjustFontSizeArgs(1) },
{ ShortcutAction::AdjustFontSize, winrt::TerminalApp::implementation::AdjustFontSizeArgs::FromJson },
{ ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson },
{ ShortcutAction::SplitVertical, LegacyParseSplitPaneArgs(SplitState::Vertical) },
{ ShortcutAction::SplitHorizontal, LegacyParseSplitPaneArgs(SplitState::Horizontal) },
{ ShortcutAction::Invalid, nullptr },
};

View File

@@ -138,6 +138,29 @@ catch (...)
namespace winrt::TerminalApp::implementation
{
// Function Description:
// - Get the AppLogic for the current active Xaml application, or null if there isn't one.
// Return value:
// - A pointer (bare) to the applogic, or nullptr. The app logic outlives all other objects,
// unless the application is in a terrible way, so this is "safe."
AppLogic* AppLogic::Current() noexcept
try
{
if (auto currentXamlApp{ winrt::Windows::UI::Xaml::Application::Current().try_as<winrt::TerminalApp::App>() })
{
if (auto appLogicPointer{ winrt::get_self<AppLogic>(currentXamlApp.Logic()) })
{
return appLogicPointer;
}
}
return nullptr;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return nullptr;
}
AppLogic::AppLogic() :
_dialogLock{},
_loadedInitialSettings{ false },
@@ -218,7 +241,7 @@ namespace winrt::TerminalApp::implementation
_root->Loaded({ this, &AppLogic::_OnLoaded });
_root->Create();
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
_ApplyTheme(_settings->GlobalSettings().GetTheme());
TraceLoggingWrite(
g_hTerminalAppProvider,
@@ -260,7 +283,27 @@ namespace winrt::TerminalApp::implementation
// IMPORTANT: Set the requested theme of the dialog, because the
// PopupRoot isn't directly in the Xaml tree of our root. So the dialog
// won't inherit our RequestedTheme automagically.
dialog.RequestedTheme(_settings->GlobalSettings().GetRequestedTheme());
// GH#5195, GH#3654 Because we cannot set RequestedTheme at the application level,
// we occasionally run into issues where parts of our UI end up themed incorrectly.
// Dialogs, for example, live under a different Xaml root element than the rest of
// our application. This makes our popup menus and buttons "disappear" when the
// user wants Terminal to be in a different theme than the rest of the system.
// This hack---and it _is_ a hack--walks up a dialog's ancestry and forces the
// theme on each element up to the root. We're relying a bit on Xaml's implementation
// details here, but it does have the desired effect.
// It's not enough to set the theme on the dialog alone.
auto themingLambda{ [this](const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&) {
auto theme{ _settings->GlobalSettings().GetTheme() };
auto element{ sender.try_as<winrt::Windows::UI::Xaml::FrameworkElement>() };
while (element)
{
element.RequestedTheme(theme);
element = element.Parent().try_as<winrt::Windows::UI::Xaml::FrameworkElement>();
}
} };
themingLambda(dialog, nullptr); // if it's already in the tree
auto loadedRevoker{ dialog.Loaded(winrt::auto_revoke, themingLambda) }; // if it's not yet in the tree
// Display the dialog.
co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup);
@@ -502,7 +545,7 @@ namespace winrt::TerminalApp::implementation
LoadSettings();
}
return _settings->GlobalSettings().GetRequestedTheme();
return _settings->GlobalSettings().GetTheme();
}
bool AppLogic::GetShowTabsInTitlebar()
@@ -617,7 +660,7 @@ namespace winrt::TerminalApp::implementation
void AppLogic::_RegisterSettingsChange()
{
// Get the containing folder.
std::filesystem::path settingsPath{ CascadiaSettings::GetSettingsPath() };
const auto settingsPath{ CascadiaSettings::GetSettingsPath() };
const auto folder = settingsPath.parent_path();
_reader.create(folder.c_str(),
@@ -683,7 +726,7 @@ namespace winrt::TerminalApp::implementation
co_await winrt::resume_foreground(_root->Dispatcher());
// Refresh the UI theme
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
_ApplyTheme(_settings->GlobalSettings().GetTheme());
}
// Method Description:
@@ -842,6 +885,65 @@ namespace winrt::TerminalApp::implementation
return { L"" };
}
winrt::hstring AppLogic::ApplicationDisplayName() const
{
try
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
return package.DisplayName();
}
CATCH_LOG();
return RS_(L"ApplicationDisplayNameUnpackaged");
}
winrt::hstring AppLogic::ApplicationVersion() const
{
try
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
const auto version{ package.Id().Version() };
winrt::hstring formatted{ wil::str_printf<std::wstring>(L"%u.%u.%u.%u", version.Major, version.Minor, version.Build, version.Revision) };
return formatted;
}
CATCH_LOG();
// Try to get the version the old-fashioned way
try
{
struct LocalizationInfo
{
WORD language, codepage;
};
// Use the current module instance handle for TerminalApp.dll, nullptr for WindowsTerminal.exe
auto filename{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
auto size{ GetFileVersionInfoSizeExW(0, filename.c_str(), nullptr) };
THROW_LAST_ERROR_IF(size == 0);
auto versionBuffer{ std::make_unique<std::byte[]>(size) };
THROW_IF_WIN32_BOOL_FALSE(GetFileVersionInfoExW(0, filename.c_str(), 0, size, versionBuffer.get()));
// Get the list of Version localizations
LocalizationInfo* pVarLocalization{ nullptr };
UINT varLen{ 0 };
THROW_IF_WIN32_BOOL_FALSE(VerQueryValueW(versionBuffer.get(), L"\\VarFileInfo\\Translation", reinterpret_cast<void**>(&pVarLocalization), &varLen));
THROW_HR_IF(E_UNEXPECTED, varLen < sizeof(*pVarLocalization)); // there must be at least one translation
// Get the product version from the localized version compartment
// We're using String/ProductVersion here because our build pipeline puts more rich information in it (like the branch name)
// than in the unlocalized numeric version fields.
WCHAR* pProductVersion{ nullptr };
UINT versionLen{ 0 };
const auto localizedVersionName{ wil::str_printf<std::wstring>(L"\\StringFileInfo\\%04x%04x\\ProductVersion",
pVarLocalization->language ? pVarLocalization->language : 0x0409, // well-known en-US LCID
pVarLocalization->codepage) };
THROW_IF_WIN32_BOOL_FALSE(VerQueryValueW(versionBuffer.get(), localizedVersionName.c_str(), reinterpret_cast<void**>(&pProductVersion), &versionLen));
return { pProductVersion };
}
CATCH_LOG();
return RS_(L"ApplicationVersionUnknown");
}
// -------------------------------- 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

@@ -15,6 +15,8 @@ namespace winrt::TerminalApp::implementation
struct AppLogic : AppLogicT<AppLogic>
{
public:
static AppLogic* Current() noexcept;
AppLogic();
~AppLogic() = default;
@@ -28,6 +30,9 @@ namespace winrt::TerminalApp::implementation
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
winrt::hstring EarlyExitMessage();
winrt::hstring ApplicationDisplayName() const;
winrt::hstring ApplicationVersion() const;
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();

View File

@@ -35,6 +35,9 @@ namespace TerminalApp
String Title { get; };
String ApplicationDisplayName { get; };
String ApplicationVersion { get; };
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
Windows.UI.Xaml.ElementTheme GetRequestedTheme();

View File

@@ -11,6 +11,7 @@
#include "../../inc/DefaultSettings.h"
#include "AppLogic.h"
#include "Utils.h"
#include "LibraryResources.h"
#include "PowershellCoreProfileGenerator.h"
#include "WslDistroGenerator.h"
@@ -27,18 +28,17 @@ static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///Prof
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" };
static constexpr std::wstring_view DEFAULT_LINUX_ICON_GUID{ L"{9acb9455-ca41-5af7-950f-6bca1bc9722f}" };
// make sure this matches defaults.json.
static constexpr std::wstring_view DEFAULT_WINDOWS_POWERSHELL_GUID{ L"{61c54bbd-c2c6-5271-96e7-009a87ff44bf}" };
// Method Description:
// - Returns the settings currently in use by the entire Terminal application.
// Throws:
// - HR E_INVALIDARG if the app isn't up and running.
const CascadiaSettings& CascadiaSettings::GetCurrentAppSettings()
{
auto currentXamlApp{ winrt::Windows::UI::Xaml::Application::Current().as<winrt::TerminalApp::App>() };
THROW_HR_IF_NULL(E_INVALIDARG, currentXamlApp);
auto appLogic = winrt::get_self<winrt::TerminalApp::implementation::AppLogic>(currentXamlApp.Logic());
auto appLogic{ ::winrt::TerminalApp::implementation::AppLogic::Current() };
THROW_HR_IF_NULL(E_INVALIDARG, appLogic);
return *(appLogic->GetSettings());
}
@@ -70,7 +70,7 @@ CascadiaSettings::CascadiaSettings(const bool addDynamicProfiles)
// - profileName: the name of the profile's GUID to return.
// Return Value:
// - the GUID associated with the profile name.
std::optional<GUID> CascadiaSettings::FindGuid(const std::wstring& profileName) const noexcept
std::optional<GUID> CascadiaSettings::FindGuid(const std::wstring_view profileName) const noexcept
{
std::optional<GUID> profileGuid{};
@@ -244,7 +244,7 @@ void CascadiaSettings::_ValidateProfilesHaveGuid()
}
// Method Description:
// - Checks if the "globals.defaultProfile" is set to one of the profiles we
// - Checks if the "defaultProfile" is set to one of the profiles we
// actually have. If the value is unset, or the value is set to something that
// doesn't exist in the list of profiles, we'll arbitrarily pick the first
// profile to use temporarily as the default.
@@ -673,3 +673,40 @@ void CascadiaSettings::_ValidateKeybindings()
_warnings.insert(_warnings.end(), keybindingWarnings.begin(), keybindingWarnings.end());
}
}
// Method Description
// - Replaces known tokens DEFAULT_PROFILE, PRODUCT and VERSION in the settings template
// with their expected values. DEFAULT_PROFILE is updated to match PowerShell Core's GUID
// if such a profile is detected. If it isn't, it'll be set to Windows PowerShell's GUID.
// Arguments:
// - settingsTemplate: a settings template
// Return value:
// - The new settings string.
std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::string_view settingsTemplate) const
{
std::string finalSettings{ settingsTemplate };
auto replace{ [](std::string& haystack, std::string_view needle, std::string_view replacement) {
auto pos{ std::string::npos };
while ((pos = haystack.rfind(needle, pos)) != std::string::npos)
{
haystack.replace(pos, needle.size(), replacement);
}
} };
std::wstring defaultProfileGuid{ DEFAULT_WINDOWS_POWERSHELL_GUID };
if (const auto psCoreProfileGuid{ FindGuid(PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()) })
{
defaultProfileGuid = Utils::GuidToString(*psCoreProfileGuid);
}
replace(finalSettings, "%DEFAULT_PROFILE%", til::u16u8(defaultProfileGuid));
if (const auto appLogic{ winrt::TerminalApp::implementation::AppLogic::Current() })
{
replace(finalSettings, "%VERSION%", til::u16u8(appLogic->ApplicationVersion()));
replace(finalSettings, "%PRODUCT%", til::u16u8(appLogic->ApplicationDisplayName()));
}
replace(finalSettings, "%COMMAND_PROMPT_LOCALIZED_NAME%", RS_A(L"CommandPromptDisplayName"));
return finalSettings;
}

View File

@@ -66,10 +66,10 @@ public:
static std::unique_ptr<CascadiaSettings> FromJson(const Json::Value& json);
void LayerJson(const Json::Value& json);
static std::wstring GetSettingsPath(const bool useRoamingPath = false);
static std::wstring GetDefaultSettingsPath();
static std::filesystem::path GetSettingsPath();
static std::filesystem::path GetDefaultSettingsPath();
std::optional<GUID> FindGuid(const std::wstring& profileName) const noexcept;
std::optional<GUID> FindGuid(const std::wstring_view profileName) const noexcept;
const Profile* FindProfile(GUID profileGuid) const noexcept;
std::vector<TerminalApp::SettingsLoadWarnings>& GetWarnings();
@@ -95,6 +95,7 @@ private:
static const Json::Value& _GetDisabledProfileSourcesJsonObject(const Json::Value& json);
bool _PrependSchemaDirective();
bool _AppendDynamicProfilesToUserSettings();
std::string _ApplyFirstRunChangesToSettingsTemplate(std::string_view settingsTemplate) const;
void _ApplyDefaultsFromUserSettings();

View File

@@ -13,7 +13,7 @@
// defaults.h is a file containing the default json settings in a std::string_view
#include "defaults.h"
#include "defaults-universal.h"
// userDefault.h is like the above, but with a default template for the user's profiles.json.
// userDefault.h is like the above, but with a default template for the user's settings.json.
#include "userDefaults.h"
// Both defaults.h and userDefaults.h are generated at build time into the
// "Generated Files" directory.
@@ -23,7 +23,8 @@ using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::TerminalApp;
using namespace ::Microsoft::Console;
static constexpr std::wstring_view SettingsFilename{ L"profiles.json" };
static constexpr std::wstring_view SettingsFilename{ L"settings.json" };
static constexpr std::wstring_view LegacySettingsFilename{ L"profiles.json" };
static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" };
static constexpr std::wstring_view DefaultsFilename{ L"defaults.json" };
@@ -33,7 +34,6 @@ static constexpr std::string_view ProfilesKey{ "profiles" };
static constexpr std::string_view DefaultSettingsKey{ "defaults" };
static constexpr std::string_view ProfilesListKey{ "list" };
static constexpr std::string_view KeybindingsKey{ "keybindings" };
static constexpr std::string_view GlobalsKey{ "globals" };
static constexpr std::string_view SchemesKey{ "schemes" };
static constexpr std::string_view DisabledProfileSourcesKey{ "disabledProfileSources" };
@@ -48,7 +48,7 @@ static constexpr std::string_view SettingsSchemaFragment{ "\n"
// it will load the settings from our packaged localappdata. If we're
// running as an unpackaged application, it will read it from the path
// we've set under localappdata.
// - Loads both the settings from the defaults.json and the user's profiles.json
// - Loads both the settings from the defaults.json and the user's settings.json
// - Also runs and dynamic profile generators. If any of those generators create
// new profiles, we'll write the user settings back to the file, with the new
// profiles inserted into their list of profiles.
@@ -74,20 +74,22 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll()
{
resultPtr->_ParseJsonString(fileData.value(), false);
}
else
// Load profiles from dynamic profile generators. _userSettings should be
// created by now, because we're going to check in there for any generators
// that should be disabled (if the user had any settings.)
resultPtr->_LoadDynamicProfiles();
if (!fileHasData)
{
// We didn't find the user settings. We'll need to create a file
// to use as the user defaults.
// For now, just parse our user settings template as their user settings.
resultPtr->_ParseJsonString(UserSettingsJson, false);
auto userSettings{ resultPtr->_ApplyFirstRunChangesToSettingsTemplate(UserSettingsJson) };
resultPtr->_ParseJsonString(userSettings, false);
needToWriteFile = true;
}
// Load profiles from dynamic profile generators. _userSettings should be
// created by now, because we're going to check in there for any generators
// that should be disabled.
resultPtr->_LoadDynamicProfiles();
// See microsoft/terminal#2325: find the defaultSettings from the user's
// settings. Layer those settings upon all the existing profiles we have
// (defaults and dynamic profiles). We'll also set
@@ -501,19 +503,8 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::FromJson(const Json::Value&
// <none>
void CascadiaSettings::LayerJson(const Json::Value& json)
{
// microsoft/terminal#2906: First layer the root object as our globals. If
// there is also a `globals` object, layer that one on top of the settings
// from the root.
_globals.LayerJson(json);
if (auto globals{ json[GlobalsKey.data()] })
{
if (globals.isObject())
{
_globals.LayerJson(globals);
}
}
if (auto schemes{ json[SchemesKey.data()] })
{
for (auto schemeJson : schemes)
@@ -619,7 +610,6 @@ void CascadiaSettings::_ApplyDefaultsFromUserSettings()
// If `profiles` was an object, then look for the `defaults` object
// underneath it for the default profile settings.
auto defaultSettings{ Json::Value::null };
if (const auto profiles{ _userSettings[JsonKey(ProfilesKey)] })
{
if (profiles.isObject())
@@ -628,6 +618,8 @@ void CascadiaSettings::_ApplyDefaultsFromUserSettings()
}
}
// cache and apply default profile settings
// from user settings file
if (defaultSettings)
{
_userDefaultProfileSettings = defaultSettings;
@@ -757,33 +749,31 @@ std::optional<std::string> CascadiaSettings::_ReadUserSettings()
if (!hFile)
{
// GH#1770 - Now that we're _not_ roaming our settings, do a quick check
// to see if there's a file in the Roaming App data folder. If there is
// a file there, but not in the LocalAppData, it's likely the user is
// upgrading from a version of the terminal from before this change.
// We'll try moving the file from the Roaming app data folder to the
// local appdata folder.
// GH#5186 - We moved from profiles.json to settings.json; we want to
// migrate any file we find. We're using MoveFile in case their settings.json
// is a symbolic link.
auto pathToLegacySettingsFile{ pathToSettingsFile };
pathToLegacySettingsFile.replace_filename(LegacySettingsFilename);
const auto pathToRoamingSettingsFile{ CascadiaSettings::GetSettingsPath(true) };
wil::unique_hfile hRoamingFile{ CreateFileW(pathToRoamingSettingsFile.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr) };
wil::unique_hfile hLegacyFile{ CreateFileW(pathToLegacySettingsFile.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr) };
if (hRoamingFile)
if (hLegacyFile)
{
// Close the file handle, move it, and re-open the file in its new location.
hRoamingFile.reset();
hLegacyFile.reset();
// Note: We're unsure if this is unsafe. Theoretically it's possible
// that two instances of the app will try and move the settings file
// simultaneously. We don't know what might happen in that scenario,
// but we're also not sure how to safely lock the file to prevent
// that from occurring.
THROW_LAST_ERROR_IF(!MoveFile(pathToRoamingSettingsFile.c_str(),
THROW_LAST_ERROR_IF(!MoveFile(pathToLegacySettingsFile.c_str(),
pathToSettingsFile.c_str()));
hFile.reset(CreateFileW(pathToSettingsFile.c_str(),
@@ -841,22 +831,18 @@ std::optional<std::string> CascadiaSettings::_ReadFile(HANDLE hFile)
// package, or in its unpackaged location. This path is under the "Local
// AppData" folder, so it _doesn't_ roam to other machines.
// - If the application is unpackaged,
// the file will end up under e.g. C:\Users\admin\AppData\Local\Microsoft\Windows Terminal\profiles.json
// the file will end up under e.g. C:\Users\admin\AppData\Local\Microsoft\Windows Terminal\settings.json
// Arguments:
// - <none>
// Return Value:
// - the full path to the settings file
std::wstring CascadiaSettings::GetSettingsPath(const bool useRoamingPath)
std::filesystem::path CascadiaSettings::GetSettingsPath()
{
wil::unique_cotaskmem_string localAppDataFolder;
// KF_FLAG_FORCE_APP_DATA_REDIRECTION, when engaged, causes SHGet... to return
// the new AppModel paths (Packages/xxx/RoamingState, etc.) for standard path requests.
// Using this flag allows us to avoid Windows.Storage.ApplicationData completely.
const auto knowFolderId = useRoamingPath ? FOLDERID_RoamingAppData : FOLDERID_LocalAppData;
if (FAILED(SHGetKnownFolderPath(knowFolderId, KF_FLAG_FORCE_APP_DATA_REDIRECTION, nullptr, &localAppDataFolder)))
{
THROW_LAST_ERROR();
}
THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_FORCE_APP_DATA_REDIRECTION, nullptr, &localAppDataFolder));
std::filesystem::path parentDirectoryForSettingsFile{ localAppDataFolder.get() };
@@ -871,7 +857,7 @@ std::wstring CascadiaSettings::GetSettingsPath(const bool useRoamingPath)
return parentDirectoryForSettingsFile / SettingsFilename;
}
std::wstring CascadiaSettings::GetDefaultSettingsPath()
std::filesystem::path CascadiaSettings::GetDefaultSettingsPath()
{
// Both of these posts suggest getting the path to the exe, then removing
// the exe's name to get the package root:
@@ -919,10 +905,9 @@ const Json::Value& CascadiaSettings::_GetProfilesJsonObject(const Json::Value& j
// given object
const Json::Value& CascadiaSettings::_GetDisabledProfileSourcesJsonObject(const Json::Value& json)
{
// Check the globals first, then look in the root.
if (json.isMember(JsonKey(GlobalsKey)))
if (!json)
{
return json[JsonKey(GlobalsKey)][JsonKey(DisabledProfileSourcesKey)];
return Json::Value::nullSingleton();
}
return json[JsonKey(DisabledProfileSourcesKey)];
}

View File

@@ -14,7 +14,6 @@ using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::Microsoft::Terminal::TerminalControl;
static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view TableKey{ "colors" };
static constexpr std::string_view ForegroundKey{ "foreground" };
static constexpr std::string_view BackgroundKey{ "background" };
static constexpr std::string_view SelectionBackgroundKey{ "selectionBackground" };
@@ -177,22 +176,6 @@ void ColorScheme::LayerJson(const Json::Value& json)
_cursorColor = color;
}
// Legacy Deserialization. Leave in place to allow forward compatibility
if (auto table{ json[JsonKey(TableKey)] })
{
int i = 0;
for (const auto& tableEntry : table)
{
if (tableEntry.isString())
{
auto color = Utils::ColorFromHexString(tableEntry.asString());
_table.at(i) = color;
}
i++;
}
}
int i = 0;
for (const auto& current : TableColors)
{

View File

@@ -21,7 +21,7 @@ TerminalApp::Profile CreateDefaultProfile(const std::wstring_view name)
gsl::as_bytes(gsl::make_span(name))) };
TerminalApp::Profile newProfile{ profileGuid };
newProfile.SetName(static_cast<std::wstring>(name));
newProfile.SetName(name);
std::wstring iconPath{ PACKAGED_PROFILE_ICON_PATH };
iconPath.append(Microsoft::Console::Utils::GuidToString(profileGuid));

View File

@@ -25,13 +25,14 @@ static constexpr std::string_view InitialColsKey{ "initialCols" };
static constexpr std::string_view RowsToScrollKey{ "rowsToScroll" };
static constexpr std::string_view InitialPositionKey{ "initialPosition" };
static constexpr std::string_view ShowTitleInTitlebarKey{ "showTerminalTitleInTitlebar" };
static constexpr std::string_view RequestedThemeKey{ "requestedTheme" };
static constexpr std::string_view ThemeKey{ "theme" };
static constexpr std::string_view TabWidthModeKey{ "tabWidthMode" };
static constexpr std::wstring_view EqualTabWidthModeValue{ L"equal" };
static constexpr std::wstring_view TitleLengthTabWidthModeValue{ L"titleLength" };
static constexpr std::string_view ShowTabsInTitlebarKey{ "showTabsInTitlebar" };
static constexpr std::string_view WordDelimitersKey{ "wordDelimiters" };
static constexpr std::string_view CopyOnSelectKey{ "copyOnSelect" };
static constexpr std::string_view CopyFormattingKey{ "copyFormatting" };
static constexpr std::string_view LaunchModeKey{ "launchMode" };
static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" };
static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" };
@@ -63,10 +64,11 @@ GlobalAppSettings::GlobalAppSettings() :
_initialY{},
_showTitleInTitlebar{ true },
_showTabsInTitlebar{ true },
_requestedTheme{ ElementTheme::Default },
_theme{ ElementTheme::Default },
_tabWidthMode{ TabViewWidthMode::Equal },
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
_copyOnSelect{ false },
_copyFormatting{ false },
_launchMode{ LaunchMode::DefaultMode },
_debugFeatures{ debugFeaturesDefault }
{
@@ -121,14 +123,14 @@ void GlobalAppSettings::SetShowTitleInTitlebar(const bool showTitleInTitlebar) n
_showTitleInTitlebar = showTitleInTitlebar;
}
ElementTheme GlobalAppSettings::GetRequestedTheme() const noexcept
ElementTheme GlobalAppSettings::GetTheme() const noexcept
{
return _requestedTheme;
return _theme;
}
void GlobalAppSettings::SetRequestedTheme(const ElementTheme requestedTheme) noexcept
void GlobalAppSettings::SetTheme(const ElementTheme theme) noexcept
{
_requestedTheme = requestedTheme;
_theme = theme;
}
TabViewWidthMode GlobalAppSettings::GetTabWidthMode() const noexcept
@@ -161,6 +163,11 @@ void GlobalAppSettings::SetCopyOnSelect(const bool copyOnSelect) noexcept
_copyOnSelect = copyOnSelect;
}
bool GlobalAppSettings::GetCopyFormatting() const noexcept
{
return _copyFormatting;
}
LaunchMode GlobalAppSettings::GetLaunchMode() const noexcept
{
return _launchMode;
@@ -245,8 +252,9 @@ Json::Value GlobalAppSettings::ToJson() const
jsonObject[JsonKey(ShowTabsInTitlebarKey)] = _showTabsInTitlebar;
jsonObject[JsonKey(WordDelimitersKey)] = winrt::to_string(_wordDelimiters);
jsonObject[JsonKey(CopyOnSelectKey)] = _copyOnSelect;
jsonObject[JsonKey(CopyFormattingKey)] = _copyFormatting;
jsonObject[JsonKey(LaunchModeKey)] = winrt::to_string(_SerializeLaunchMode(_launchMode));
jsonObject[JsonKey(RequestedThemeKey)] = winrt::to_string(_SerializeTheme(_requestedTheme));
jsonObject[JsonKey(ThemeKey)] = winrt::to_string(_SerializeTheme(_theme));
jsonObject[JsonKey(TabWidthModeKey)] = winrt::to_string(_SerializeTabWidthMode(_tabWidthMode));
jsonObject[JsonKey(KeybindingsKey)] = _keybindings->ToJson();
jsonObject[JsonKey(ConfirmCloseAllKey)] = _confirmCloseAllTabs;
@@ -311,14 +319,16 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
JsonUtils::GetBool(json, CopyOnSelectKey, _copyOnSelect);
JsonUtils::GetBool(json, CopyFormattingKey, _copyFormatting);
if (auto launchMode{ json[JsonKey(LaunchModeKey)] })
{
_launchMode = _ParseLaunchMode(GetWstringFromJson(launchMode));
}
if (auto requestedTheme{ json[JsonKey(RequestedThemeKey)] })
if (auto theme{ json[JsonKey(ThemeKey)] })
{
_requestedTheme = _ParseTheme(GetWstringFromJson(requestedTheme));
_theme = _ParseTheme(GetWstringFromJson(theme));
}
if (auto tabWidthMode{ json[JsonKey(TabWidthModeKey)] })

View File

@@ -53,8 +53,10 @@ public:
bool GetConfirmCloseAllTabs() const noexcept;
void SetConfirmCloseAllTabs(const bool confirmCloseAllTabs) noexcept;
void SetRequestedTheme(const winrt::Windows::UI::Xaml::ElementTheme requestedTheme) noexcept;
winrt::Windows::UI::Xaml::ElementTheme GetTheme() const noexcept;
void SetTheme(const winrt::Windows::UI::Xaml::ElementTheme requestedTheme) noexcept;
winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode GetTabWidthMode() const noexcept;
void SetTabWidthMode(const winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode tabWidthMode);
bool GetShowTabsInTitlebar() const noexcept;
@@ -66,6 +68,8 @@ public:
bool GetCopyOnSelect() const noexcept;
void SetCopyOnSelect(const bool copyOnSelect) noexcept;
bool GetCopyFormatting() const noexcept;
std::optional<int32_t> GetInitialX() const noexcept;
std::optional<int32_t> GetInitialY() const noexcept;
@@ -73,10 +77,6 @@ public:
winrt::TerminalApp::LaunchMode GetLaunchMode() const noexcept;
void SetLaunchMode(const winrt::TerminalApp::LaunchMode launchMode);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme() const noexcept;
winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode GetTabWidthMode() const noexcept;
bool DebugFeaturesEnabled() const noexcept;
Json::Value ToJson() const;
@@ -112,7 +112,8 @@ private:
bool _showTabsInTitlebar;
std::wstring _wordDelimiters;
bool _copyOnSelect;
winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;
bool _copyFormatting;
winrt::Windows::UI::Xaml::ElementTheme _theme;
winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode _tabWidthMode;
winrt::TerminalApp::LaunchMode _launchMode;

View File

@@ -21,6 +21,7 @@ static constexpr std::wstring_view POWERSHELL_PREVIEW_PFN{ L"Microsoft.PowerShel
static constexpr std::wstring_view PWSH_EXE{ L"pwsh.exe" };
static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" };
static constexpr std::wstring_view POWERSHELL_PREVIEW_ICON{ L"ms-appx:///ProfileIcons/pwsh-preview.png" };
static constexpr std::wstring_view POWERSHELL_PREFERRED_PROFILE_NAME{ L"PowerShell" };
namespace
{
@@ -322,8 +323,17 @@ std::vector<TerminalApp::Profile> PowershellCoreProfileGenerator::GenerateProfil
// (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version.
auto firstProfile = profiles.begin();
firstProfile->SetGuid(PowershellCoreGuid);
firstProfile->SetName(L"PowerShell");
firstProfile->SetName(POWERSHELL_PREFERRED_PROFILE_NAME);
}
return profiles;
}
// Function Description:
// - Returns the thing it's named for.
// Return value:
// - the thing it says in the name
const std::wstring_view PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()
{
return POWERSHELL_PREFERRED_PROFILE_NAME;
}

View File

@@ -23,6 +23,8 @@ namespace TerminalApp
class PowershellCoreProfileGenerator : public TerminalApp::IDynamicProfileGenerator
{
public:
static const std::wstring_view GetPreferredPowershellProfileName();
PowershellCoreProfileGenerator() = default;
~PowershellCoreProfileGenerator() = default;
std::wstring_view GetNamespace() override;

View File

@@ -19,13 +19,11 @@ static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view GuidKey{ "guid" };
static constexpr std::string_view SourceKey{ "source" };
static constexpr std::string_view ColorSchemeKey{ "colorScheme" };
static constexpr std::string_view ColorSchemeKeyOld{ "colorscheme" };
static constexpr std::string_view HiddenKey{ "hidden" };
static constexpr std::string_view ForegroundKey{ "foreground" };
static constexpr std::string_view BackgroundKey{ "background" };
static constexpr std::string_view SelectionBackgroundKey{ "selectionBackground" };
static constexpr std::string_view ColorTableKey{ "colorTable" };
static constexpr std::string_view TabTitleKey{ "tabTitle" };
static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" };
static constexpr std::string_view HistorySizeKey{ "historySize" };
@@ -105,7 +103,6 @@ Profile::Profile(const std::optional<GUID>& guid) :
_defaultBackground{},
_selectionBackground{},
_cursorColor{},
_colorTable{},
_tabTitle{},
_suppressApplicationTitle{},
_historySize{ DEFAULT_HISTORY_SIZE },
@@ -171,11 +168,6 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
TerminalSettings terminalSettings{};
// Fill in the Terminal Setting's CoreSettings from the profile
auto const colorTableCount = gsl::narrow_cast<int>(_colorTable.size());
for (int i = 0; i < colorTableCount; i++)
{
terminalSettings.SetColorTableEntry(i, _colorTable[i]);
}
terminalSettings.HistorySize(_historySize);
terminalSettings.SnapOnInput(_snapOnInput);
terminalSettings.CursorHeight(_cursorHeight);
@@ -308,15 +300,6 @@ Json::Value Profile::ToJson() const
const auto scheme = winrt::to_string(_schemeName.value());
root[JsonKey(ColorSchemeKey)] = scheme;
}
else
{
Json::Value tableArray{};
for (auto& color : _colorTable)
{
tableArray.append(Utils::ColorToHexString(color));
}
root[JsonKey(ColorTableKey)] = tableArray;
}
root[JsonKey(HistorySizeKey)] = _historySize;
root[JsonKey(SnapOnInputKey)] = _snapOnInput;
// Only add the cursor height property if we're a legacy-style cursor.
@@ -644,26 +627,6 @@ void Profile::LayerJson(const Json::Value& json)
JsonUtils::GetOptionalColor(json, CursorColorKey, _cursorColor);
JsonUtils::GetOptionalString(json, ColorSchemeKey, _schemeName);
// TODO:GH#1069 deprecate old settings key
JsonUtils::GetOptionalString(json, ColorSchemeKeyOld, _schemeName);
// Only look for the "table" if there's no "schemeName"
if (!(json.isMember(JsonKey(ColorSchemeKey))) &&
!(json.isMember(JsonKey(ColorSchemeKeyOld))) &&
json.isMember(JsonKey(ColorTableKey)))
{
auto colortable{ json[JsonKey(ColorTableKey)] };
int i = 0;
for (const auto& tableEntry : colortable)
{
if (tableEntry.isString())
{
const auto color = Utils::ColorFromHexString(tableEntry.asString());
_colorTable[i] = color;
}
i++;
}
}
// TODO:MSFT:20642297 - Use a sentinel value (-1) for "Infinite scrollback"
JsonUtils::GetInt(json, HistorySizeKey, _historySize);
@@ -755,9 +718,9 @@ void Profile::SetStartingDirectory(std::wstring startingDirectory) noexcept
_startingDirectory = std::move(startingDirectory);
}
void Profile::SetName(std::wstring name) noexcept
void Profile::SetName(const std::wstring_view name) noexcept
{
_name = std::move(name);
_name = static_cast<std::wstring>(name);
}
void Profile::SetUseAcrylic(bool useAcrylic) noexcept

View File

@@ -79,7 +79,7 @@ public:
void SetAcrylicOpacity(double opacity) noexcept;
void SetCommandline(std::wstring cmdline) noexcept;
void SetStartingDirectory(std::wstring startingDirectory) noexcept;
void SetName(std::wstring name) noexcept;
void SetName(const std::wstring_view name) noexcept;
void SetUseAcrylic(bool useAcrylic) noexcept;
void SetDefaultForeground(COLORREF defaultForeground) noexcept;
void SetDefaultBackground(COLORREF defaultBackground) noexcept;
@@ -143,7 +143,6 @@ private:
std::optional<uint32_t> _defaultBackground;
std::optional<uint32_t> _selectionBackground;
std::optional<uint32_t> _cursorColor;
std::array<uint32_t, COLOR_TABLE_SIZE> _colorTable;
std::optional<std::wstring> _tabTitle;
bool _suppressApplicationTitle;
int32_t _historySize;

View File

@@ -163,7 +163,7 @@ Temporarily using the Windows Terminal default settings.
<value>Failed to reload settings</value>
</data>
<data name="FeedbackUriValue" xml:space="preserve">
<value>https://aka.ms/terminal-feedback</value>
<value>https://go.microsoft.com/fwlink/?linkid=2125419</value>
</data>
<data name="AboutMenuItem" xml:space="preserve">
<value>About</value>
@@ -266,11 +266,11 @@ Temporarily using the Windows Terminal default settings.
<value>Privacy Policy</value>
<comment>A hyperlink name for the Terminal's privacy policy</comment>
</data>
<data name="AboutDialog_DisplayNameUnpackaged" xml:space="preserve">
<data name="ApplicationDisplayNameUnpackaged" xml:space="preserve">
<value>Windows Terminal (Unpackaged)</value>
<comment>This display name is used when the application's name cannot be determined</comment>
</data>
<data name="AboutDialog_VersionUnknown" xml:space="preserve">
<data name="ApplicationVersionUnknown" xml:space="preserve">
<value>Unknown</value>
<comment>This is displayed when the version of the application cannot be determined</comment>
</data>
@@ -283,4 +283,8 @@ Temporarily using the Windows Terminal default settings.
<data name="CloseAllDialog.Title" xml:space="preserve">
<value>Do you want to close all tabs?</value>
</data>
</root>
<data name="CommandPromptDisplayName" xml:space="preserve">
<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>
</root>

View File

@@ -33,11 +33,6 @@ namespace winrt::TerminalApp::implementation
_CopyTextHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::CopyTextWithoutNewlines:
{
_CopyTextHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::PasteText:
{
_PasteTextHandlers(*this, *eventArgs);
@@ -60,15 +55,6 @@ namespace winrt::TerminalApp::implementation
}
case ShortcutAction::NewTab:
case ShortcutAction::NewTabProfile0:
case ShortcutAction::NewTabProfile1:
case ShortcutAction::NewTabProfile2:
case ShortcutAction::NewTabProfile3:
case ShortcutAction::NewTabProfile4:
case ShortcutAction::NewTabProfile5:
case ShortcutAction::NewTabProfile6:
case ShortcutAction::NewTabProfile7:
case ShortcutAction::NewTabProfile8:
{
_NewTabHandlers(*this, *eventArgs);
break;
@@ -136,46 +122,24 @@ namespace winrt::TerminalApp::implementation
}
case ShortcutAction::SwitchToTab:
case ShortcutAction::SwitchToTab0:
case ShortcutAction::SwitchToTab1:
case ShortcutAction::SwitchToTab2:
case ShortcutAction::SwitchToTab3:
case ShortcutAction::SwitchToTab4:
case ShortcutAction::SwitchToTab5:
case ShortcutAction::SwitchToTab6:
case ShortcutAction::SwitchToTab7:
case ShortcutAction::SwitchToTab8:
{
_SwitchToTabHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::ResizePane:
case ShortcutAction::ResizePaneLeft:
case ShortcutAction::ResizePaneRight:
case ShortcutAction::ResizePaneUp:
case ShortcutAction::ResizePaneDown:
{
_ResizePaneHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::MoveFocus:
case ShortcutAction::MoveFocusLeft:
case ShortcutAction::MoveFocusRight:
case ShortcutAction::MoveFocusUp:
case ShortcutAction::MoveFocusDown:
{
_MoveFocusHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::IncreaseFontSize:
{
_AdjustFontSizeHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::DecreaseFontSize:
case ShortcutAction::AdjustFontSize:
{
_AdjustFontSizeHandlers(*this, *eventArgs);
break;

View File

@@ -11,20 +11,10 @@ namespace TerminalApp
{
Invalid = 0,
CopyText,
CopyTextWithoutNewlines,
PasteText,
OpenNewTabDropdown,
DuplicateTab,
NewTab,
NewTabProfile0, // Legacy
NewTabProfile1, // Legacy
NewTabProfile2, // Legacy
NewTabProfile3, // Legacy
NewTabProfile4, // Legacy
NewTabProfile5, // Legacy
NewTabProfile6, // Legacy
NewTabProfile7, // Legacy
NewTabProfile8, // Legacy
NewWindow,
CloseWindow,
CloseTab,
@@ -35,32 +25,14 @@ namespace TerminalApp
SplitHorizontal,
SplitPane,
SwitchToTab,
SwitchToTab0, // Legacy
SwitchToTab1, // Legacy
SwitchToTab2, // Legacy
SwitchToTab3, // Legacy
SwitchToTab4, // Legacy
SwitchToTab5, // Legacy
SwitchToTab6, // Legacy
SwitchToTab7, // Legacy
SwitchToTab8, // Legacy
IncreaseFontSize,
DecreaseFontSize,
AdjustFontSize,
ResetFontSize,
ScrollUp,
ScrollDown,
ScrollUpPage,
ScrollDownPage,
ResizePane,
ResizePaneLeft, // Legacy
ResizePaneRight, // Legacy
ResizePaneUp, // Legacy
ResizePaneDown, // Legacy
MoveFocus,
MoveFocusLeft, // Legacy
MoveFocusRight, // Legacy
MoveFocusUp, // Legacy
MoveFocusDown, // Legacy
Find,
ToggleFullscreen,
OpenSettings

View File

@@ -5,6 +5,7 @@
#include "TerminalPage.h"
#include "ActionAndArgs.h"
#include "Utils.h"
#include "AppLogic.h"
#include "../../types/inc/utils.hpp"
#include <LibraryResources.h>
@@ -234,28 +235,22 @@ namespace winrt::TerminalApp::implementation
winrt::hstring TerminalPage::ApplicationDisplayName()
{
try
if (const auto appLogic{ implementation::AppLogic::Current() })
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
return package.DisplayName();
return appLogic->ApplicationDisplayName();
}
CATCH_LOG();
return RS_(L"AboutDialog_DisplayNameUnpackaged");
return RS_(L"ApplicationDisplayNameUnpackaged");
}
winrt::hstring TerminalPage::ApplicationVersion()
{
try
if (const auto appLogic{ implementation::AppLogic::Current() })
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
const auto version{ package.Id().Version() };
winrt::hstring formatted{ wil::str_printf<std::wstring>(L"%u.%u.%u.%u", version.Major, version.Minor, version.Build, version.Revision) };
return formatted;
return appLogic->ApplicationVersion();
}
CATCH_LOG();
return RS_(L"AboutDialog_VersionUnknown");
return RS_(L"ApplicationVersionUnknown");
}
// Method Description:
@@ -292,25 +287,16 @@ namespace winrt::TerminalApp::implementation
// add the keyboard shortcuts for the first 9 profiles
if (profileIndex < 9)
{
// enum value for ShortcutAction::NewTabProfileX; 0==NewTabProfile0
const auto action = static_cast<ShortcutAction>(profileIndex + static_cast<int>(ShortcutAction::NewTabProfile0));
// First, attempt to search for the keybinding for the simple
// NewTabProfile0-9 ShortcutActions.
auto profileKeyChord = keyBindings.GetKeyBindingForAction(action);
if (!profileKeyChord)
{
// If NewTabProfileN didn't have a binding, look for a
// keychord that is bound to the equivalent
// NewTab(ProfileIndex=N) action
auto actionAndArgs = winrt::make_self<winrt::TerminalApp::implementation::ActionAndArgs>();
actionAndArgs->Action(ShortcutAction::NewTab);
auto newTabArgs = winrt::make_self<winrt::TerminalApp::implementation::NewTabArgs>();
auto newTerminalArgs = winrt::make_self<winrt::TerminalApp::implementation::NewTerminalArgs>();
newTerminalArgs->ProfileIndex(profileIndex);
newTabArgs->TerminalArgs(*newTerminalArgs);
actionAndArgs->Args(*newTabArgs);
profileKeyChord = keyBindings.GetKeyBindingForActionWithArgs(*actionAndArgs);
}
// Look for a keychord that is bound to the equivalent
// NewTab(ProfileIndex=N) action
auto actionAndArgs = winrt::make_self<winrt::TerminalApp::implementation::ActionAndArgs>();
actionAndArgs->Action(ShortcutAction::NewTab);
auto newTabArgs = winrt::make_self<winrt::TerminalApp::implementation::NewTabArgs>();
auto newTerminalArgs = winrt::make_self<winrt::TerminalApp::implementation::NewTerminalArgs>();
newTerminalArgs->ProfileIndex(profileIndex);
newTabArgs->TerminalArgs(*newTerminalArgs);
actionAndArgs->Args(*newTabArgs);
auto profileKeyChord{ keyBindings.GetKeyBindingForActionWithArgs(*actionAndArgs) };
// make sure we find one to display
if (profileKeyChord)
@@ -1324,18 +1310,21 @@ namespace winrt::TerminalApp::implementation
// copy text to dataPack
dataPack.SetText(copiedData.Text());
// copy html to dataPack
const auto htmlData = copiedData.Html();
if (!htmlData.empty())
if (_settings->GlobalSettings().GetCopyFormatting())
{
dataPack.SetHtmlFormat(htmlData);
}
// copy html to dataPack
const auto htmlData = copiedData.Html();
if (!htmlData.empty())
{
dataPack.SetHtmlFormat(htmlData);
}
// copy rtf data to dataPack
const auto rtfData = copiedData.Rtf();
if (!rtfData.empty())
{
dataPack.SetRtf(rtfData);
// copy rtf data to dataPack
const auto rtfData = copiedData.Rtf();
if (!rtfData.empty())
{
dataPack.SetRtf(rtfData);
}
}
try
@@ -1385,14 +1374,13 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Copy text from the focused terminal to the Windows Clipboard
// Arguments:
// - trimTrailingWhitespace: enable removing any whitespace from copied selection
// and get text to appear on separate lines.
// - singleLine: if enabled, copy contents as a single line of text
// Return Value:
// - true iff we we able to copy text (if a selection was active)
bool TerminalPage::_CopyText(const bool trimTrailingWhitespace)
bool TerminalPage::_CopyText(const bool singleLine)
{
const auto control = _GetActiveControl();
return control.CopySelectionToClipboard(!trimTrailingWhitespace);
return control.CopySelectionToClipboard(singleLine);
}
// Method Description:

View File

@@ -33,16 +33,16 @@ the MIT License. See LICENSE in the project root for license information. -->
</TextBlock>
<HyperlinkButton
x:Uid="AboutDialog_GettingStartedLink"
NavigateUri="https://aka.ms/terminal-getting-started" />
NavigateUri="https://go.microsoft.com/fwlink/?linkid=2125715" />
<HyperlinkButton
x:Uid="AboutDialog_DocumentationLink"
NavigateUri="https://aka.ms/terminal-documentation" />
NavigateUri="https://go.microsoft.com/fwlink/?linkid=2125416" />
<HyperlinkButton
x:Uid="AboutDialog_ReleaseNotesLink"
NavigateUri="https://aka.ms/terminal-release-notes" />
NavigateUri="https://go.microsoft.com/fwlink/?linkid=2125417" />
<HyperlinkButton
x:Uid="AboutDialog_PrivacyPolicyLink"
NavigateUri="https://aka.ms/terminal-privacy-policy" />
NavigateUri="https://go.microsoft.com/fwlink/?linkid=2125418" />
</StackPanel>
</ContentDialog>

View File

@@ -1,14 +1,28 @@
// THIS IS AN AUTO-GENERATED FILE! Changes to this file will be ignored.
{
"alwaysShowTabs": true,
"defaultProfile": "{550ce7b8-d500-50ad-8a1a-c400c3262db3}",
// Launch Settings
"initialCols": 120,
"initialRows": 30,
"requestedTheme": "system",
"showTabsInTitlebar": false,
"showTerminalTitleInTitlebar": true,
"launchMode": "default",
// Selection
"copyOnSelect": false,
"wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502",
// Tab UI
"alwaysShowTabs": true,
"showTabsInTitlebar": true,
"showTerminalTitleInTitlebar": true,
"tabWidthMode": "equal",
// Miscellaneous
"confirmCloseAllTabs": true,
"theme": "system",
"rowsToScroll": "system",
"snapToGridOnResize": true,
"profiles":
[
{
@@ -59,54 +73,68 @@
],
"keybindings":
[
{ "command": "closePane", "keys": [ "ctrl+shift+w" ] },
{ "command": "closeWindow", "keys": [ "alt+f4" ] },
{ "command": "copy", "keys": [ "ctrl+shift+c" ] },
{ "command": "copy", "keys": [ "ctrl+insert" ] },
{ "command": "decreaseFontSize", "keys": [ "ctrl+-" ] },
{ "command": "duplicateTab", "keys": [ "ctrl+shift+d" ] },
{ "command": "increaseFontSize", "keys": [ "ctrl+=" ] },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": [ "alt+down" ] },
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": [ "alt+left" ] },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": [ "alt+right" ] },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": [ "alt+up" ] },
{ "command": "newTab", "keys": [ "ctrl+shift+t" ] },
{ "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+1"] },
{ "command": { "action": "newTab", "index": 1 }, "keys": ["ctrl+shift+2"] },
{ "command": { "action": "newTab", "index": 2 }, "keys": ["ctrl+shift+3"] },
{ "command": { "action": "newTab", "index": 3 }, "keys": ["ctrl+shift+4"] },
{ "command": { "action": "newTab", "index": 4 }, "keys": ["ctrl+shift+5"] },
{ "command": { "action": "newTab", "index": 5 }, "keys": ["ctrl+shift+6"] },
{ "command": { "action": "newTab", "index": 6 }, "keys": ["ctrl+shift+7"] },
{ "command": { "action": "newTab", "index": 7 }, "keys": ["ctrl+shift+8"] },
{ "command": { "action": "newTab", "index": 8 }, "keys": ["ctrl+shift+9"] },
{ "command": "nextTab", "keys": [ "ctrl+tab" ] },
{ "command": "openNewTabDropdown", "keys": [ "ctrl+shift+space" ] },
{ "command": "openSettings", "keys": [ "ctrl+," ] },
{ "command": "paste", "keys": [ "ctrl+shift+v" ] },
{ "command": "paste", "keys": [ "shift+insert" ] },
{ "command": "prevTab", "keys": [ "ctrl+shift+tab" ] },
{ "command": "resetFontSize", "keys": ["ctrl+0"]},
{ "command": { "action": "resizePane", "direction": "down" }, "keys": [ "alt+shift+down" ] },
{ "command": { "action": "resizePane", "direction": "left" }, "keys": [ "alt+shift+left" ] },
{ "command": { "action": "resizePane", "direction": "right" }, "keys": [ "alt+shift+right" ] },
{ "command": { "action": "resizePane", "direction": "up" }, "keys": [ "alt+shift+up" ] },
{ "command": "scrollDown", "keys": [ "ctrl+shift+down" ] },
{ "command": "scrollDownPage", "keys": [ "ctrl+shift+pgdn" ] },
{ "command": "scrollUp", "keys": [ "ctrl+shift+up" ] },
{ "command": "scrollUpPage", "keys": [ "ctrl+shift+pgup" ] },
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": [ "alt+shift+-" ] },
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": [ "alt+shift+plus" ] },
{ "command": { "action": "switchToTab", "index": 0 }, "keys": ["ctrl+alt+1"] },
{ "command": { "action": "switchToTab", "index": 1 }, "keys": ["ctrl+alt+2"] },
{ "command": { "action": "switchToTab", "index": 2 }, "keys": ["ctrl+alt+3"] },
{ "command": { "action": "switchToTab", "index": 3 }, "keys": ["ctrl+alt+4"] },
{ "command": { "action": "switchToTab", "index": 4 }, "keys": ["ctrl+alt+5"] },
{ "command": { "action": "switchToTab", "index": 5 }, "keys": ["ctrl+alt+6"] },
{ "command": { "action": "switchToTab", "index": 6 }, "keys": ["ctrl+alt+7"] },
{ "command": { "action": "switchToTab", "index": 7 }, "keys": ["ctrl+alt+8"] },
{ "command": { "action": "switchToTab", "index": 8 }, "keys": ["ctrl+alt+9"] },
{ "command": "toggleFullscreen", "keys": [ "alt+enter" ] },
{ "command": "toggleFullscreen", "keys": [ "f11" ] }
// Application-level Keys
{ "command": "closeWindow", "keys": "alt+f4" },
{ "command": "toggleFullscreen", "keys": "alt+enter" },
{ "command": "toggleFullscreen", "keys": "f11" },
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space" },
{ "command": "openSettings", "keys": "ctrl+," },
{ "command": "find", "keys": "ctrl+shift+f" },
// Tab Management
// "command": "closeTab" is unbound by default.
// The closeTab command closes a tab without confirmation, even if it has multiple panes.
{ "command": "newTab", "keys": "ctrl+shift+t" },
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" },
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" },
{ "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3" },
{ "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4" },
{ "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5" },
{ "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6" },
{ "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7" },
{ "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8" },
{ "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9" },
{ "command": "duplicateTab", "keys": "ctrl+shift+d" },
{ "command": "nextTab", "keys": "ctrl+tab" },
{ "command": "prevTab", "keys": "ctrl+shift+tab" },
{ "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1" },
{ "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2" },
{ "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3" },
{ "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4" },
{ "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5" },
{ "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6" },
{ "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7" },
{ "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8" },
{ "command": { "action": "switchToTab", "index": 8 }, "keys": "ctrl+alt+9" },
// Pane Management
{ "command": "closePane", "keys": "ctrl+shift+w" },
{ "command": { "action": "splitPane", "split": "horizontal" }, "keys": "alt+shift+-" },
{ "command": { "action": "splitPane", "split": "vertical" }, "keys": "alt+shift+plus" },
{ "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" },
{ "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" },
{ "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" },
{ "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up" },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" },
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
// Clipboard Integration
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c" },
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert" },
{ "command": "paste", "keys": "ctrl+shift+v" },
{ "command": "paste", "keys": "shift+insert" },
// Scrollback
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },
{ "command": "scrollUp", "keys": "ctrl+shift+up" },
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
// Visual Adjustments
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+=" },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+-" },
{ "command": "resetFontSize", "keys": "ctrl+0" }
]
}

View File

@@ -1,16 +1,27 @@
// THIS IS AN AUTO-GENERATED FILE! Changes to this file will be ignored.
{
"alwaysShowTabs": true,
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
// Launch Settings
"initialCols": 120,
"initialRows": 30,
"requestedTheme": "system",
"launchMode": "default",
// Selection
"copyOnSelect": false,
"wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502",
// Tab UI
"alwaysShowTabs": true,
"showTabsInTitlebar": true,
"showTerminalTitleInTitlebar": true,
"tabWidthMode": "equal",
"snapToGridOnResize": true,
"wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502",
// Miscellaneous
"confirmCloseAllTabs": true,
"theme": "system",
"rowsToScroll": "system",
"snapToGridOnResize": true,
"profiles":
[
@@ -18,41 +29,45 @@
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe",
"hidden": false,
"startingDirectory": "%USERPROFILE%",
"closeOnExit": "graceful",
"icon": "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
"colorScheme": "Campbell Powershell",
"antialiasingMode": "grayscale",
"closeOnExit": "graceful",
"cursorShape": "bar",
"fontFace": "Cascadia Code",
"fontSize": 12,
"hidden": false,
"historySize": 9001,
"icon": "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
"padding": "8, 8, 8, 8",
"snapOnInput": true,
"useAcrylic": false,
"antialiasingMode": "grayscale"
"startingDirectory": "%USERPROFILE%",
"useAcrylic": false
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"name": "Command Prompt",
"commandline": "cmd.exe",
"hidden": false,
"startingDirectory": "%USERPROFILE%",
"closeOnExit": "graceful",
"icon": "ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png",
"colorScheme": "Campbell",
"antialiasingMode": "grayscale",
"closeOnExit": "graceful",
"cursorShape": "bar",
"fontFace": "Cascadia Code",
"fontSize": 12,
"hidden": false,
"historySize": 9001,
"icon": "ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png",
"padding": "8, 8, 8, 8",
"snapOnInput": true,
"useAcrylic": false,
"antialiasingMode": "grayscale"
"startingDirectory": "%USERPROFILE%",
"useAcrylic": false
}
],
"schemes":
[
// A profile can override the following color scheme values:
// - "foreground"
// - "background"
// - "cursorColor"
{
"name": "Campbell",
"foreground": "#CCCCCC",
@@ -210,17 +225,17 @@
],
"keybindings":
[
{ "command": "closePane", "keys": "ctrl+shift+w" },
// Application-level Keys
{ "command": "closeWindow", "keys": "alt+f4" },
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "copy", "keys": "ctrl+insert" },
{ "command": "decreaseFontSize", "keys": "ctrl+-" },
{ "command": "duplicateTab", "keys": "ctrl+shift+d" },
{ "command": "increaseFontSize", "keys": "ctrl+=" },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" },
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
{ "command": "toggleFullscreen", "keys": "alt+enter" },
{ "command": "toggleFullscreen", "keys": "f11" },
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space" },
{ "command": "openSettings", "keys": "ctrl+," },
{ "command": "find", "keys": "ctrl+shift+f" },
// Tab Management
// "command": "closeTab" is unbound by default.
// The closeTab command closes a tab without confirmation, even if it has multiple panes.
{ "command": "newTab", "keys": "ctrl+shift+t" },
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" },
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" },
@@ -231,23 +246,9 @@
{ "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7" },
{ "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8" },
{ "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9" },
{ "command": "duplicateTab", "keys": "ctrl+shift+d" },
{ "command": "nextTab", "keys": "ctrl+tab" },
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space" },
{ "command": "openSettings", "keys": "ctrl+," },
{ "command": "paste", "keys": "ctrl+shift+v" },
{ "command": "paste", "keys": "shift+insert" },
{ "command": "prevTab", "keys": "ctrl+shift+tab" },
{ "command": "resetFontSize", "keys": "ctrl+0" },
{ "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" },
{ "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" },
{ "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" },
{ "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up" },
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },
{ "command": "scrollUp", "keys": "ctrl+shift+up" },
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": "alt+shift+-" },
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": "alt+shift+plus" },
{ "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1" },
{ "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2" },
{ "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3" },
@@ -257,8 +258,35 @@
{ "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7" },
{ "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8" },
{ "command": { "action": "switchToTab", "index": 8 }, "keys": "ctrl+alt+9" },
{ "command": "find", "keys": "ctrl+shift+f" },
{ "command": "toggleFullscreen", "keys": "alt+enter" },
{ "command": "toggleFullscreen", "keys": "f11" }
// Pane Management
{ "command": "closePane", "keys": "ctrl+shift+w" },
{ "command": { "action": "splitPane", "split": "horizontal" }, "keys": "alt+shift+-" },
{ "command": { "action": "splitPane", "split": "vertical" }, "keys": "alt+shift+plus" },
{ "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" },
{ "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" },
{ "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" },
{ "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up" },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" },
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
// Clipboard Integration
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c" },
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert" },
{ "command": "paste", "keys": "ctrl+shift+v" },
{ "command": "paste", "keys": "shift+insert" },
// Scrollback
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },
{ "command": "scrollUp", "keys": "ctrl+shift+up" },
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
// Visual Adjustments
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+=" },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+-" },
{ "command": "resetFontSize", "keys": "ctrl+0" }
]
}

View File

@@ -9,6 +9,8 @@
#define WIN32_LEAN_AND_MEAN
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
#include <LibraryIncludes.h>
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
// SDK definition of this function, so the only fix is to undef it.
@@ -63,3 +65,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <CLI11/CLI11.hpp>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"

View File

@@ -1,40 +1,72 @@
// This file was initially generated by %PRODUCT% %VERSION%
// It should still be usable in newer versions, but newer versions might have additional
// settings, help text, or changes that you will not see unless you clear this file
// and let us generate a new one for you.
// To view the default settings, hold "alt" while clicking on the "Settings" button.
// For documentation on these settings, see: https://aka.ms/terminal-documentation
{
"$schema": "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"defaultProfile": "%DEFAULT_PROFILE%",
// You can add more global application settings here.
// To learn more about global settings, visit https://aka.ms/terminal-global-settings
// If enabled, selections are automatically copied to your clipboard.
"copyOnSelect": false,
// A profile specifies a command to execute paired with information about how it should look and feel.
// Each one of them will appear in the 'New Tab' dropdown,
// and can be invoked from the commandline with `wt.exe -p xxx`
// To learn more about profiles, visit https://aka.ms/terminal-profile-settings
"profiles":
{
"defaults":
{
// Put settings here that you want to apply to all profiles
// Put settings here that you want to apply to all profiles.
},
"list":
[
{
// Make changes here to the powershell.exe profile
// Make changes here to the powershell.exe profile.
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe",
"hidden": false
},
{
// Make changes here to the cmd.exe profile
// Make changes here to the cmd.exe profile.
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"name": "%COMMAND_PROMPT_LOCALIZED_NAME%",
"commandline": "cmd.exe",
"hidden": false
}
]
},
// Add custom color schemes to this array
// Add custom color schemes to this array.
// To learn more about color schemes, visit https://aka.ms/terminal-color-schemes
"schemes": [],
// Add any keybinding overrides to this array.
// To unbind a default keybinding, set the command to "unbound"
"keybindings": []
// Add custom keybindings to this array.
// To unbind a key combination from your defaults.json, set the command to "unbound".
// To learn more about keybindings, visit https://aka.ms/terminal-keybindings
"keybindings":
[
// Copy and paste are bound to Ctrl+Shift+C and Ctrl+Shift+V in your defaults.json.
// These two lines additionally bind them to Ctrl+C and Ctrl+V.
// To learn more about selection, visit https://aka.ms/terminal-selection
{ "command": {"action": "copy", "singleLine": false }, "keys": "ctrl+c" },
{ "command": "paste", "keys": "ctrl+v" },
// Press Ctrl+Shift+F to open the search box
{ "command": "find", "keys": "ctrl+shift+f" },
// Press Alt+Shift+D to open a new pane.
// - "split": "auto" makes this pane open in the direction that provides the most surface area.
// - "splitMode": "duplicate" makes the new pane use the focused pane's profile.
// To learn more about panes, visit https://aka.ms/terminal-panes
{ "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" }
]
}

View File

@@ -12,14 +12,15 @@
#define BLOCK_GSL
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
#include <LibraryIncludes.h>
// Must be included before any WinRT headers.
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <wil/cppwinrt.h>
#include "winrt/Windows.Foundation.h"
#include "winrt/Windows.Security.Credentials.h"
#include "winrt/Windows.Foundation.Collections.h"
#include <Windows.h>
@@ -27,3 +28,5 @@
#include <TraceLoggingProvider.h>
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalConnectionProvider);
#include <telemetry/ProjectTelemetry.h>
#include "til.h"

View File

@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.TerminalControl
{
// This interface is a hack for GH#979. Controls should implement this
// interface to be able to be notified of mousewheel events even on devices
// who's trackpads won't scroll inactive windows.
[uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")]
interface IMouseWheelListener
{
Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta);
}
}

View File

@@ -19,7 +19,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
TSFInputControl::TSFInputControl() :
_editContext{ nullptr },
_inComposition{ false },
_activeTextStart{ 0 }
_activeTextStart{ 0 },
_focused{ false },
_currentTerminalCursorPos{ 0, 0 },
_currentCanvasWidth{ 0.0 },
_currentTextBlockHeight{ 0.0 },
_currentTextBounds{ 0, 0, 0, 0 },
_currentControlBounds{ 0, 0, 0, 0 }
{
InitializeComponent();
@@ -58,7 +64,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// Explicitly disconnect the LayoutRequested handler -- it can cause problems during application teardown.
// See GH#4159 for more info.
// Also disconnect compositionCompleted and textUpdating explicitly. It seems to occasionally cause problems if
// a composition is active during application teardown.
_layoutRequestedRevoker.revoke();
_compositionCompletedRevoker.revoke();
_textUpdatingRevoker.revoke();
}
// Method Description:
@@ -73,6 +83,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_editContext != nullptr)
{
_editContext.NotifyFocusEnter();
_focused = true;
}
}
@@ -88,6 +99,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_editContext != nullptr)
{
_editContext.NotifyFocusLeave();
_focused = false;
}
}
@@ -114,28 +126,51 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
// Method Description:
// - Handler for LayoutRequested event by CoreEditContext responsible
// for returning the current position the IME should be placed
// in screen coordinates on the screen. TSFInputControls internal
// XAML controls (TextBlock/Canvas) are also positioned and updated.
// NOTE: documentation says application should handle this event
// - Redraw the canvas if certain dimensions have changed since the last
// redraw. This includes the Terminal cursor position, the Canvas width, and the TextBlock height.
// Arguments:
// - sender: CoreTextEditContext sending the request.
// - args: CoreTextLayoutRequestedEventArgs to be updated with position information.
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_layoutRequestedHandler(CoreTextEditContext sender, CoreTextLayoutRequestedEventArgs const& args)
void TSFInputControl::TryRedrawCanvas()
{
auto request = args.Request();
// Get window in screen coordinates, this is the entire window including tabs
const auto windowBounds = CoreWindow::GetForCurrentThread().Bounds();
if (!_focused)
{
return;
}
// Get the cursor position in text buffer position
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
_CurrentCursorPositionHandlers(*this, *cursorArgs);
const COORD cursorPos = { ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().X), ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().Y) };
const til::point cursorPos{ gsl::narrow_cast<ptrdiff_t>(cursorArgs->CurrentPosition().X), gsl::narrow_cast<ptrdiff_t>(cursorArgs->CurrentPosition().Y) };
const double actualCanvasWidth = Canvas().ActualWidth();
const double actualTextBlockHeight = TextBlock().ActualHeight();
if (_currentTerminalCursorPos == cursorPos &&
_currentCanvasWidth == actualCanvasWidth &&
_currentTextBlockHeight == actualTextBlockHeight)
{
return;
}
_currentTerminalCursorPos = cursorPos;
_currentCanvasWidth = actualCanvasWidth;
_currentTextBlockHeight = actualTextBlockHeight;
_RedrawCanvas();
}
// Method Description:
// - Redraw the Canvas and update the current Text Bounds and Control Bounds for
// the CoreTextEditContext.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_RedrawCanvas()
{
// Get Font Info as we use this is the pixel size for characters in the display
auto fontArgs = winrt::make_self<FontInfoEventArgs>();
_CurrentFontInfoHandlers(*this, *fontArgs);
@@ -145,8 +180,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Convert text buffer cursor position to client coordinate position within the window
COORD clientCursorPos;
clientCursorPos.X = ::base::ClampMul(cursorPos.X, ::base::ClampedNumeric<short>(fontWidth));
clientCursorPos.Y = ::base::ClampMul(cursorPos.Y, ::base::ClampedNumeric<short>(fontHeight));
clientCursorPos.X = ::base::ClampMul(_currentTerminalCursorPos.x(), ::base::ClampedNumeric<ptrdiff_t>(fontWidth));
clientCursorPos.Y = ::base::ClampMul(_currentTerminalCursorPos.y(), ::base::ClampedNumeric<ptrdiff_t>(fontHeight));
// position textblock to cursor position
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
Canvas().SetTop(TextBlock(), clientCursorPos.Y);
// calculate FontSize in pixels from DPIs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
TextBlock().FontSize(fontSizePx);
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
const auto widthToTerminalEnd = _currentCanvasWidth - ::base::ClampedNumeric<double>(clientCursorPos.X);
// Make sure that we're setting the MaxWidth to a positive number - a
// negative number here will crash us in mysterious ways with a useless
// stack trace
const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
TextBlock().MaxWidth(newMaxWidth);
// Get window in screen coordinates, this is the entire window including tabs
const auto windowBounds = CoreWindow::GetForCurrentThread().Bounds();
// Convert from client coordinate to screen coordinate by adding window position
COORD screenCursorPos;
@@ -162,33 +216,35 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Get scale factor for view
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
const auto yOffset = ::base::ClampedNumeric<float>(_currentTextBlockHeight) - fontHeight;
const auto textBottom = ::base::ClampedNumeric<float>(screenCursorPos.Y) + yOffset;
// position textblock to cursor position
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
Canvas().SetTop(TextBlock(), ::base::ClampedNumeric<double>(clientCursorPos.Y));
_currentTextBounds = ScaleRect(Rect(screenCursorPos.X, textBottom, 0, fontHeight), scaleFactor);
_currentControlBounds = ScaleRect(Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight), scaleFactor);
}
// calculate FontSize in pixels from DIPs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
TextBlock().FontSize(fontSizePx);
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
// Method Description:
// - Handler for LayoutRequested event by CoreEditContext responsible
// for returning the current position the IME should be placed
// in screen coordinates on the screen. TSFInputControls internal
// XAML controls (TextBlock/Canvas) are also positioned and updated.
// NOTE: documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request.
// - args: CoreTextLayoutRequestedEventArgs to be updated with position information.
// Return Value:
// - <none>
void TSFInputControl::_layoutRequestedHandler(CoreTextEditContext sender, CoreTextLayoutRequestedEventArgs const& args)
{
auto request = args.Request();
const auto canvasActualWidth = Canvas().ActualWidth();
const auto widthToTerminalEnd = canvasActualWidth - ::base::ClampedNumeric<double>(clientCursorPos.X);
// Make sure that we're setting the MaxWidth to a positive number - a
// negative number here will crash us in mysterious ways with a useless
// stack trace
const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
TextBlock().MaxWidth(newMaxWidth);
TryRedrawCanvas();
// Set the text block bounds
const auto yOffset = ::base::ClampedNumeric<float>(TextBlock().ActualHeight()) - fontHeight;
const auto textBottom = ::base::ClampedNumeric<float>(screenCursorPos.Y) + yOffset;
Rect selectionRect = Rect(screenCursorPos.X, textBottom, 0, fontHeight);
request.LayoutBounds().TextBounds(ScaleRect(selectionRect, scaleFactor));
request.LayoutBounds().TextBounds(_currentTextBounds);
// Set the control bounds of the whole control
Rect controlRect = Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight);
request.LayoutBounds().ControlBounds(ScaleRect(controlRect, scaleFactor));
request.LayoutBounds().ControlBounds(_currentControlBounds);
}
// Method Description:

View File

@@ -37,6 +37,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void NotifyFocusEnter();
void NotifyFocusLeave();
void ClearBuffer();
void TryRedrawCanvas();
void Close();
@@ -73,6 +74,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bool _inComposition;
size_t _activeTextStart;
void _SendAndClearText();
void _RedrawCanvas();
bool _focused;
til::point _currentTerminalCursorPos;
double _currentCanvasWidth;
double _currentTextBlockHeight;
winrt::Windows::Foundation::Rect _currentControlBounds;
winrt::Windows::Foundation::Rect _currentTextBounds;
};
}
namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation

View File

@@ -28,6 +28,7 @@ namespace Microsoft.Terminal.TerminalControl
void NotifyFocusEnter();
void NotifyFocusLeave();
void ClearBuffer();
void TryRedrawCanvas();
void Close();
}

View File

@@ -86,6 +86,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
auto pfnTerminalCursorPositionChanged = std::bind(&TermControl::_TerminalCursorPositionChanged, this);
_terminal->SetCursorPositionChangedCallback(pfnTerminalCursorPositionChanged);
// This event is explicitly revoked in the destructor: does not need weak_ref
auto onReceiveOutputFn = [this](const hstring str) {
_terminal->Write(str);
@@ -481,14 +484,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged()
{
{ // lock scope
auto terminalLock = _terminal->LockForReading();
if (!_initializedTerminal)
{
return;
}
}
// This event is only registered during terminal initialization,
// so we don't need to check _initializedTerminal.
// We also don't lock for things that come back from the renderer.
auto chain = _renderEngine->GetSwapChain();
auto weakThis{ get_weak() };
@@ -496,8 +494,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (auto control{ weakThis.get() })
{
auto terminalLock = _terminal->LockForWriting();
_AttachDxgiSwapChainToXaml(chain.Get());
}
}
@@ -636,11 +632,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// becomes a no-op.
this->Focus(FocusState::Programmatic);
_connection.Start();
_initializedTerminal = true;
} // scope for TerminalLock
// call this event dispatcher outside of lock
// Start the connection outside of lock, because it could
// start writing output immediately.
_connection.Start();
// Likewise, run the event handlers outside of lock (they could
// be reentrant)
_InitializedHandlers(*this, nullptr);
return true;
}
@@ -654,8 +654,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
const auto ch = e.Character();
const bool handled = _terminal->SendCharEvent(ch);
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
const auto modifiers = _GetPressedModifierKeys();
const bool handled = _terminal->SendCharEvent(ch, scanCode, modifiers);
e.Handled(handled);
}
@@ -1169,6 +1170,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - Event handler for the PointerWheelChanged event. This is raised in
// response to mouse wheel changes. Depending upon what modifier keys are
// pressed, different actions will take place.
// - Primarily just takes the data from the PointerRoutedEventArgs and uses
// it to call _DoMouseWheel, see _DoMouseWheel for more details.
// Arguments:
// - args: the event args containing information about t`he mouse wheel event.
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
@@ -1180,22 +1183,49 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
const auto point = args.GetCurrentPoint(*this);
auto result = _DoMouseWheel(point.Position(),
ControlKeyStates{ args.KeyModifiers() },
point.Properties().MouseWheelDelta(),
point.Properties().IsLeftButtonPressed());
if (result)
{
args.Handled(true);
}
}
// Method Description:
// - Actually handle a scrolling event, whether from a mouse wheel or a
// touchpad scroll. Depending upon what modifier keys are pressed,
// different actions will take place.
// * Attempts to first dispatch the mouse scroll as a VT event
// * If Ctrl+Shift are pressed, then attempts to change our opacity
// * If just Ctrl is pressed, we'll attempt to "zoom" by changing our font size
// * Otherwise, just scrolls the content of the viewport
// Arguments:
// - point: the location of the mouse during this event
// - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers
// - delta: the mouse wheel delta that triggered this event.
bool TermControl::_DoMouseWheel(const Windows::Foundation::Point point,
const ControlKeyStates modifiers,
const int32_t delta,
const bool isLeftButtonPressed)
{
if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
// Most mouse event handlers call
// _TrySendMouseEvent(point);
// here with a PointerPoint. However, as of #979, we don't have a
// PointerPoint to work with. So, we're just going to do a
// mousewheel event manually
return _terminal->SendMouseEvent(_GetTerminalPosition(point),
WM_MOUSEWHEEL,
_GetPressedModifierKeys(),
::base::saturated_cast<short>(delta));
}
const auto delta = point.Properties().MouseWheelDelta();
// Get the state of the Ctrl & Shift keys
// static_cast to a uint32_t because we can't use the WI_IsFlagSet macro
// directly with a VirtualKeyModifiers
const auto modifiers = static_cast<uint32_t>(args.KeyModifiers());
const auto ctrlPressed = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Control));
const auto shiftPressed = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Shift));
const auto ctrlPressed = modifiers.IsCtrlPressed();
const auto shiftPressed = modifiers.IsShiftPressed();
if (ctrlPressed && shiftPressed)
{
@@ -1207,8 +1237,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
else
{
_MouseScrollHandler(delta, point);
_MouseScrollHandler(delta, point, isLeftButtonPressed);
}
return false;
}
// Method Description:
// - This is part of the solution to GH#979
// - Manually handle a scrolling event. This is used to help support
// scrolling on devices where the touchpad doesn't correctly handle
// scrolling inactive windows.
// Arguments:
// - location: the location of the mouse during this event. This location is
// relative to the origin of the control
// - delta: the mouse wheel delta that triggered this event.
bool TermControl::OnMouseWheel(const Windows::Foundation::Point location,
const int32_t delta)
{
const auto modifiers = _GetPressedModifierKeys();
return _DoMouseWheel(location, modifiers, delta, false);
}
// Method Description:
@@ -1281,7 +1328,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - Scroll the visible viewport in response to a mouse wheel event.
// Arguments:
// - mouseDelta: the mouse wheel delta that triggered this event.
void TermControl::_MouseScrollHandler(const double mouseDelta, Windows::UI::Input::PointerPoint const& pointerPoint)
// - point: the location of the mouse during this event
// - isLeftButtonPressed: true iff the left mouse button was pressed during this event.
void TermControl::_MouseScrollHandler(const double mouseDelta,
const Windows::Foundation::Point point,
const bool isLeftButtonPressed)
{
const auto currentOffset = ScrollBar().Value();
@@ -1298,11 +1349,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// for us.
ScrollBar().Value(newValue);
if (_terminal->IsSelectionActive() && pointerPoint.Properties().IsLeftButtonPressed())
if (_terminal->IsSelectionActive() && isLeftButtonPressed)
{
// If user is mouse selecting and scrolls, they then point at new character.
// Make sure selection reflects that immediately.
_SetEndSelectionPointAtCursor(pointerPoint.Position());
_SetEndSelectionPointAtCursor(point);
}
}
@@ -1566,7 +1617,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// concerned with initialization process. Value forwarded to event handler.
void TermControl::_UpdateFont(const bool initialUpdate)
{
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
const float newDpi = static_cast<float>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
@@ -1629,7 +1680,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_renderEngine)
{
const auto scale = sender.CompositionScaleX();
const auto dpi = (int)(scale * USER_DEFAULT_SCREEN_DPI);
const auto dpi = (float)(scale * static_cast<float>(USER_DEFAULT_SCREEN_DPI));
// TODO: MSFT: 21169071 - Shouldn't this all happen through _renderer and trigger the invalidate automatically on DPI change?
THROW_IF_FAILED(_renderEngine->UpdateDpi(dpi));
@@ -1793,6 +1844,49 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}
// Method Description:
// - Tells TSFInputControl to redraw the Canvas/TextBlock so it'll update
// to be where the current cursor position is.
// Arguments:
// - N/A
winrt::fire_and_forget 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);
}
}
hstring TermControl::Title()
{
hstring hstr{ _terminal->GetConsoleTitle() };
@@ -1809,8 +1903,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Windows Clipboard (CascadiaWin32:main.cpp).
// - CopyOnSelect does NOT clear the selection
// Arguments:
// - collapseText: collapse all of the text to one line
bool TermControl::CopySelectionToClipboard(bool collapseText)
// - singleLine: collapse all of the text to one line
bool TermControl::CopySelectionToClipboard(bool singleLine)
{
if (_closing)
{
@@ -1827,7 +1921,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_selectionNeedsToBeCopied = false;
// extract text from buffer
const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(collapseText);
const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(singleLine);
// convert text: vector<string> --> string
std::wstring textData;
@@ -1876,6 +1970,26 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_clipboardPasteHandlers(*this, *pasteArgs);
}
// Method Description:
// - Asynchronously close our connection. The Connection will likely wait
// until the attached process terminates before Close returns. If that's
// the case, we don't want to block the UI thread waiting on that process
// handle.
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget TermControl::_AsyncCloseConnection()
{
if (auto localConnection{ std::exchange(_connection, nullptr) })
{
// Close the connection on the background thread.
co_await winrt::resume_background();
localConnection.Close();
// connection is destroyed.
}
}
void TermControl::Close()
{
if (!_closing.exchange(true))
@@ -1887,11 +2001,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
_autoScrollTimer.Stop();
if (auto localConnection{ std::exchange(_connection, nullptr) })
{
localConnection.Close();
// connection is destroyed.
}
// GH#1996 - Close the connection asynchronously on a background
// thread.
// Since TermControl::Close is only ever triggered by the UI, we
// don't really care to wait for the connection to be completely
// closed. We can just do it whenever.
_AsyncCloseConnection();
if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) })
{
@@ -1971,7 +2086,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// TODO: MSFT:21254947 - use a static function to do this instead of
// instantiating a DxEngine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
THROW_IF_FAILED(dxEngine->UpdateDpi(dpi));
THROW_IF_FAILED(dxEngine->UpdateDpi(gsl::narrow_cast<float>(dpi)));
THROW_IF_FAILED(dxEngine->UpdateFont(desiredFont, actualFont));
const float scale = dxEngine->GetScaling();
@@ -2243,8 +2358,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return;
}
const COORD cursorPos = _terminal->GetCursorPosition();
Windows::Foundation::Point p = { gsl::narrow_cast<float>(cursorPos.X), gsl::narrow_cast<float>(cursorPos.Y) };
const til::point cursorPos = _terminal->GetCursorPosition();
Windows::Foundation::Point p = { ::base::ClampedNumeric<float>(cursorPos.x()), ::base::ClampedNumeric<float>(cursorPos.y()) };
eventArgs.CurrentPosition(p);
}

View File

@@ -84,6 +84,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bool OnF7Pressed();
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta);
~TermControl();
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
@@ -196,10 +198,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _DoResize(const double newWidth, const double newHeight);
void _TerminalTitleChanged(const std::wstring_view& wstr);
winrt::fire_and_forget _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
winrt::fire_and_forget _TerminalCursorPositionChanged();
void _MouseScrollHandler(const double delta, Windows::UI::Input::PointerPoint const& pointerPoint);
void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed);
void _MouseZoomHandler(const double delta);
void _MouseTransparencyHandler(const double delta);
bool _DoMouseWheel(const Windows::Foundation::Point point, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta, const bool isLeftButtonPressed);
bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
@@ -227,6 +231,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _CompositionCompleted(winrt::hstring text);
void _CurrentCursorPositionHandler(const IInspectable& sender, const CursorPositionEventArgs& eventArgs);
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

@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "IMouseWheelListener.idl";
namespace Microsoft.Terminal.TerminalControl
{
delegate void TitleChangedEventArgs(String newTitle);
@@ -30,7 +32,7 @@ namespace Microsoft.Terminal.TerminalControl
void HandleClipboardData(String data);
}
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IF7Listener
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IF7Listener, IMouseWheelListener
{
TermControl();
TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);

View File

@@ -76,6 +76,7 @@
<DependentUpon>TermControl.xaml</DependentUpon>
</Midl>
<Midl Include="TermControlAutomationPeer.idl" />
<Midl Include="IMouseWheelListener.idl" />
<Midl Include="TSFInputControl.idl">
<DependentUpon>TSFInputControl.xaml</DependentUpon>
</Midl>

View File

@@ -9,6 +9,8 @@
#define WIN32_LEAN_AND_MEAN
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
#include <LibraryIncludes.h>
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
// SDK definition of this function, so the only fix is to undef it.
@@ -23,6 +25,7 @@
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.system.h>
#include <winrt/Windows.Graphics.Display.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
@@ -46,3 +49,5 @@
#include <TraceLoggingProvider.h>
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalControlProvider);
#include <telemetry/ProjectTelemetry.h>
#include "til.h"

View File

@@ -47,6 +47,27 @@ public:
return *this;
}
#ifdef WINRT_Windows_System_H
ControlKeyStates(const winrt::Windows::System::VirtualKeyModifiers& modifiers) noexcept :
_value{ 0 }
{
// static_cast to a uint32_t because we can't use the WI_IsFlagSet
// macro directly with a VirtualKeyModifiers
const auto m = static_cast<uint32_t>(modifiers);
_value |= WI_IsFlagSet(m, static_cast<uint32_t>(winrt::Windows::System::VirtualKeyModifiers::Shift)) ?
SHIFT_PRESSED :
0;
// Since we can't differentiate between the left & right versions of Ctrl & Alt in a VirtualKeyModifiers
_value |= WI_IsFlagSet(m, static_cast<uint32_t>(winrt::Windows::System::VirtualKeyModifiers::Menu)) ?
LEFT_ALT_PRESSED :
0;
_value |= WI_IsFlagSet(m, static_cast<uint32_t>(winrt::Windows::System::VirtualKeyModifiers::Control)) ?
LEFT_CTRL_PRESSED :
0;
}
#endif
constexpr DWORD Value() const noexcept
{
return _value;

View File

@@ -18,7 +18,7 @@ namespace Microsoft::Terminal::Core
virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) = 0;
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0;
virtual bool SendCharEvent(const wchar_t ch) = 0;
virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0;
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
[[nodiscard]] virtual HRESULT UserResize(const COORD size) noexcept = 0;

View File

@@ -387,14 +387,21 @@ bool Terminal::IsTrackingMouseInput() const noexcept
}
// Method Description:
// - Send this particular key event to the terminal. The terminal will translate
// the key and the modifiers pressed into the appropriate VT sequence for that
// key chord. If we do translate the key, we'll return true. In that case, the
// event should NOT be processed any further. If we return false, the event
// was NOT translated, and we should instead use the event to try and get the
// real character out of the event.
// - Send this particular (non-character) key event to the terminal.
// - The terminal will translate the key and the modifiers pressed into the
// appropriate VT sequence for that key chord. If we do translate the key,
// we'll return true. In that case, the event should NOT be processed any further.
// - Character events (e.g. WM_CHAR) are generally the best way to properly receive
// keyboard input on Windows though, as the OS is suited best at handling the
// translation of the current keyboard layout, dead keys, etc.
// As a result of this false is returned for all key events that contain characters.
// SendCharEvent may then be called with the data obtained from a character event.
// - As a special case we'll always handle VK_TAB key events.
// This must be done due to TermControl::_KeyDownHandler (one of the callers)
// always marking tab key events as handled, causing no character event to be raised.
// Arguments:
// - vkey: The vkey of the key pressed.
// - vkey: The vkey of the last pressed key.
// - scanCode: The scan code of the last pressed key.
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
// Return Value:
// - true if we translated the key event, and it should not be processed any further.
@@ -402,50 +409,35 @@ bool Terminal::IsTrackingMouseInput() const noexcept
bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states)
{
TrySnapOnInput();
_StoreKeyEvent(vkey, scanCode);
const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed();
// Alt key sequences _require_ the char to be in the keyevent. If alt is
// pressed, manually get the character that's being typed, and put it in the
// KeyEvent.
// DON'T manually handle Alt+Space - the system will use this to bring up
// the system menu for restore, min/maximize, size, move, close
wchar_t ch = UNICODE_NULL;
if (states.IsAltPressed() && vkey != VK_SPACE)
// the system menu for restore, min/maximize, size, move, close.
// (This doesn't apply to Ctrl+Alt+Space.)
if (isAltOnlyPressed && vkey == VK_SPACE)
{
ch = _CharacterFromKeyEvent(vkey, scanCode, states);
return false;
}
if (states.IsCtrlPressed())
{
switch (vkey)
{
case 0x48:
// Manually handle Ctrl+H. Ctrl+H should be handled as Backspace. To do this
// correctly, the keyEvents's char needs to be set to Backspace.
// 0x48 is the VKEY for 'H', which isn't named
ch = UNICODE_BACKSPACE;
break;
case VK_SPACE:
// Manually handle Ctrl+Space here. The terminalInput translator requires
// the char to be set to Space for space handling to work correctly.
ch = UNICODE_SPACE;
break;
}
}
const auto ch = _CharacterFromKeyEvent(vkey, scanCode, states);
// Manually handle Escape here. If we let it fall through, it'll come
// back up through the character handler. It's registered as a translation
// in TerminalInput, so we'll let TerminalInput control it.
if (vkey == VK_ESCAPE)
// Delegate it to the character event handler if this key event can be
// mapped to one (see method description above). For Alt+key combinations
// we'll not receive another character event for some reason though.
// -> Don't delegate the event if this is a Alt+key combination.
//
// As a special case we'll furthermore always handle VK_TAB
// key events here instead of in Terminal::SendCharEvent.
// See the method description for more information.
if (!isAltOnlyPressed && vkey != VK_TAB && ch != UNICODE_NULL)
{
ch = UNICODE_ESC;
return false;
}
const bool manuallyHandled = ch != UNICODE_NULL;
KeyEvent keyEv{ true, 0, vkey, scanCode, ch, states.Value() };
const bool translated = _terminalInput->HandleKey(&keyEv);
return translated && manuallyHandled;
return _terminalInput->HandleKey(&keyEv);
}
// Method Description:
@@ -474,9 +466,39 @@ bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButt
return _terminalInput->HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta);
}
bool Terminal::SendCharEvent(const wchar_t ch)
// Method Description:
// - Send this particular character to the terminal.
// - This method is the counterpart to SendKeyEvent and behaves almost identical.
// The difference is the focus on sending characters to the terminal,
// whereas SendKeyEvent handles the sending of keys like the arrow keys.
// Arguments:
// - ch: The UTF-16 code unit to be sent.
// - scanCode: The scan code of the last pressed key. Can be left 0.
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
// Return Value:
// - true if we translated the character event, and it should not be processed any further.
// - false otherwise.
bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states)
{
return _terminalInput->HandleChar(ch);
// DON'T manually handle Alt+Space - the system will use this to bring up
// the system menu for restore, min/maximize, size, move, close.
if (ch == L' ' && states.IsAltPressed() && !states.IsCtrlPressed())
{
return false;
}
auto vkey = _TakeVirtualKeyFromLastKeyEvent(scanCode);
if (vkey == 0 && scanCode != 0)
{
vkey = _VirtualKeyFromScanCode(scanCode);
}
if (vkey == 0)
{
vkey = _VirtualKeyFromCharacter(ch);
}
KeyEvent keyEv{ true, 0, vkey, scanCode, ch, states.Value() };
return _terminalInput->HandleKey(&keyEv);
}
// Method Description:
@@ -490,6 +512,29 @@ WORD Terminal::_ScanCodeFromVirtualKey(const WORD vkey) noexcept
return LOWORD(MapVirtualKeyW(vkey, MAPVK_VK_TO_VSC));
}
// Method Description:
// - Returns the virtual key code for the given keyboard's scan code.
// Arguments:
// - scanCode: The keyboard's scan code.
// Return Value:
// - The virtual key code. 0 if no mapping can be found.
WORD Terminal::_VirtualKeyFromScanCode(const WORD scanCode) noexcept
{
return LOWORD(MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK));
}
// Method Description:
// - Returns any virtual key code that produces the given character.
// Arguments:
// - scanCode: The keyboard's scan code.
// Return Value:
// - The virtual key code. 0 if no mapping can be found.
WORD Terminal::_VirtualKeyFromCharacter(const wchar_t ch) noexcept
{
const auto vkey = LOWORD(VkKeyScanW(ch));
return vkey == -1 ? 0 : vkey;
}
// Method Description:
// - Translates the specified virtual key code and keyboard state to the corresponding character.
// Arguments:
@@ -532,6 +577,40 @@ catch (...)
return UNICODE_INVALID;
}
// Method Description:
// - It's possible for a single scan code on a keyboard to
// produce different key codes depending on the keyboard state.
// MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK) will always chose one of the
// possibilities no matter what though and thus can't be used in SendCharEvent.
// - This method stores the key code from a key event (SendKeyEvent).
// If the key event contains character data, handling of the event will be
// denied, in order to delegate the work to the character event handler.
// - The character event handler (SendCharEvent) will now pick up
// the stored key code to restore the full key event data.
// Arguments:
// - vkey: The virtual key code.
// - scanCode: The scan code.
void Terminal::_StoreKeyEvent(const WORD vkey, const WORD scanCode)
{
_lastKeyEventCodes.emplace(KeyEventCodes{ vkey, scanCode });
}
// Method Description:
// - This method acts as a counterpart to _StoreKeyEvent and extracts a stored
// key code. As a safety measure it'll ensure that the given scan code
// matches the stored scan code from the previous key event.
// - See _StoreKeyEvent for more information.
// Arguments:
// - scanCode: The scan code.
// Return Value:
// - The key code matching the given scan code. Otherwise 0.
WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept
{
const auto codes = _lastKeyEventCodes.value_or(KeyEventCodes{});
_lastKeyEventCodes.reset();
return codes.ScanCode == scanCode ? codes.VirtualKey : 0;
}
// Method Description:
// - Acquire a read lock on the terminal.
// Return Value:
@@ -704,6 +783,8 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
_buffer->GetRenderTarget().TriggerRedrawAll();
_NotifyScrollEvent();
}
_NotifyTerminalCursorPositionChanged();
}
void Terminal::UserScrollViewport(const int viewTop)
@@ -736,6 +817,14 @@ try
}
CATCH_LOG()
void Terminal::_NotifyTerminalCursorPositionChanged() noexcept
{
if (_pfnCursorPositionChanged)
{
_pfnCursorPositionChanged();
}
}
void Terminal::SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept
{
_pfnWriteInput.swap(pfn);
@@ -751,6 +840,11 @@ void Terminal::SetScrollPositionChangedCallback(std::function<void(const int, co
_pfnScrollPositionChanged.swap(pfn);
}
void Terminal::SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept
{
_pfnCursorPositionChanged.swap(pfn);
}
// Method Description:
// - Allows setting a callback for when the background color is changed
// Arguments:

View File

@@ -115,7 +115,7 @@ public:
// These methods are defined in Terminal.cpp
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states) override;
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override;
bool SendCharEvent(const wchar_t ch) override;
bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override;
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
void UserScrollViewport(const int viewTop) override;
@@ -168,6 +168,7 @@ public:
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;
void SetCursorOn(const bool isOn) noexcept;
@@ -194,6 +195,7 @@ private:
std::function<void(const std::wstring_view&)> _pfnTitleChanged;
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
std::function<void(const uint32_t)> _pfnBackgroundColorChanged;
std::function<void()> _pfnCursorPositionChanged;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput;
@@ -249,9 +251,22 @@ private:
// underneath them, while others would prefer to anchor it in place.
// Either way, we should make this behavior controlled by a setting.
// Since virtual keys are non-zero, you assume that this field is empty/invalid if it is.
struct KeyEventCodes
{
WORD VirtualKey;
WORD ScanCode;
};
std::optional<KeyEventCodes> _lastKeyEventCodes;
static WORD _ScanCodeFromVirtualKey(const WORD vkey) noexcept;
static WORD _VirtualKeyFromScanCode(const WORD scanCode) noexcept;
static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept;
static wchar_t _CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept;
void _StoreKeyEvent(const WORD vkey, const WORD scanCode);
WORD _TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept;
int _VisibleStartIndex() const noexcept;
int _VisibleEndIndex() const noexcept;
@@ -266,6 +281,8 @@ private:
void _NotifyScrollEvent() noexcept;
void _NotifyTerminalCursorPositionChanged() noexcept;
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;

View File

@@ -221,18 +221,18 @@ void Terminal::ClearSelection()
// Method Description:
// - get wstring text from highlighted portion of text buffer
// Arguments:
// - collapseText: collapse all of the text to one line
// - singleLine: collapse all of the text to one line
// Return Value:
// - wstring text from buffer. If extended to multiple lines, each line is separated by \r\n
const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool collapseText) const
const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool singleLine) const
{
const auto selectionRects = _GetSelectionRects();
std::function<COLORREF(TextAttribute&)> GetForegroundColor = std::bind(&Terminal::GetForegroundColor, this, std::placeholders::_1);
std::function<COLORREF(TextAttribute&)> GetBackgroundColor = std::bind(&Terminal::GetBackgroundColor, this, std::placeholders::_1);
return _buffer->GetText(!collapseText,
!collapseText,
return _buffer->GetText(!singleLine,
!singleLine,
selectionRects,
GetForegroundColor,
GetBackgroundColor);

View File

@@ -23,6 +23,7 @@
class InputBuffer; // This for some reason needs to be fwd-decl'd
#include "../host/inputBuffer.hpp"
#include "../host/readDataCooked.hpp"
#include "../host/output.h"
#include "test/CommonState.hpp"
#include "../cascadia/TerminalCore/Terminal.hpp"
@@ -50,6 +51,9 @@ using namespace TerminalCoreUnitTests;
class TerminalCoreUnitTests::ConptyRoundtripTests final
{
// !!! DANGER: Many tests in this class expect the Terminal and Host buffers
// to be 80x32. If you change these, you'll probably inadvertently break a
// bunch of tests !!!
static const SHORT TerminalViewWidth = 80;
static const SHORT TerminalViewHeight = 32;
@@ -174,6 +178,16 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(TestResizeHeight);
TEST_METHOD(OutputWrappedLinesAtTopOfBuffer);
TEST_METHOD(OutputWrappedLinesAtBottomOfBuffer);
TEST_METHOD(ScrollWithChangesInMiddle);
TEST_METHOD(DontWrapMoveCursorInSingleFrame);
TEST_METHOD(ClearHostTrickeryTest);
TEST_METHOD(OverstrikeAtBottomOfBuffer);
TEST_METHOD(MarginsWithStatusLine);
TEST_METHOD(OutputWrappedLineWithSpace);
TEST_METHOD(OutputWrappedLineWithSpaceAtBottomOfBuffer);
TEST_METHOD(ScrollWithMargins);
private:
@@ -950,7 +964,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
else
{
// After we hit the bottom of the viewport, the newlines come in
// separated for whatever reason.
// separated by empty writes for whatever reason.
expectedOutput.push_back("\r");
expectedOutput.push_back("\n");
expectedOutput.push_back("");
@@ -1026,8 +1040,7 @@ void ConptyRoundtripTests::PassthroughHardReset()
else
{
// After we hit the bottom of the viewport, the newlines come in
// separated for whatever reason.
// separated by empty writes for whatever reason.
expectedOutput.push_back("\r");
expectedOutput.push_back("\n");
expectedOutput.push_back("");
@@ -1061,6 +1074,293 @@ void ConptyRoundtripTests::PassthroughHardReset()
}
}
void ConptyRoundtripTests::OutputWrappedLinesAtTopOfBuffer()
{
Log::Comment(
L"Case 1: Write a wrapped line right at the start of the buffer, before any circling");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
const auto wrappedLineLength = TerminalViewWidth + 20;
sm.ProcessString(std::wstring(wrappedLineLength, L'A'));
auto verifyBuffer = [](const TextBuffer& tb) {
// Buffer contents should look like the following: (80 wide)
// (w) means we hard wrapped the line
// (b) means the line is _not_ wrapped (it's broken, the default state.)
// cursor is on the '_'
//
// |AAAAAAAA...AAAA| (w)
// |AAAAA_ ... | (b) (There are 20 'A's on this line.)
// | ... | (b)
VERIFY_IS_TRUE(tb.GetRowByOffset(0).GetCharRow().WasWrapForced());
VERIFY_IS_FALSE(tb.GetRowByOffset(1).GetCharRow().WasWrapForced());
auto iter0 = tb.GetCellDataAt({ 0, 0 });
TestUtils::VerifySpanOfText(L"A", iter0, 0, TerminalViewWidth);
auto iter1 = tb.GetCellDataAt({ 0, 1 });
TestUtils::VerifySpanOfText(L"A", iter1, 0, 20);
auto iter2 = tb.GetCellDataAt({ 20, 1 });
TestUtils::VerifySpanOfText(L" ", iter2, 0, TerminalViewWidth - 20);
};
verifyBuffer(hostTb);
expectedOutput.push_back(std::string(TerminalViewWidth, 'A'));
expectedOutput.push_back(std::string(20, 'A'));
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
}
void ConptyRoundtripTests::OutputWrappedLinesAtBottomOfBuffer()
{
Log::Comment(
L"Case 2: Write a wrapped line at the end of the buffer, once the conpty started circling");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
// First, fill the buffer with contents, so conpty starts circling
const auto hostView = si.GetViewport();
const auto end = 2 * hostView.Height();
for (auto i = 0; i < end; i++)
{
Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end));
expectedOutput.push_back("X");
if (i < hostView.BottomInclusive())
{
expectedOutput.push_back("\r\n");
}
else
{
// After we hit the bottom of the viewport, the newlines come in
// separated by empty writes for whatever reason.
expectedOutput.push_back("\r");
expectedOutput.push_back("\n");
expectedOutput.push_back("");
}
hostSm.ProcessString(L"X\n");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
const auto wrappedLineLength = TerminalViewWidth + 20;
// The following diagrams show the buffer contents after each string emitted
// from conpty. For each of these diagrams:
// (w) means we hard wrapped the line
// (b) means the line is _not_ wrapped (it's broken, the default state.)
// cursor is on the '_'
// Initial state:
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |_ | (b)
expectedOutput.push_back(std::string(TerminalViewWidth, 'A'));
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AAAA|_ (w) The cursor is actually on the last A here
// TODO GH#5228 might break the "newline & repaint the wrapped char" checks here, that's okay.
expectedOutput.push_back("\r"); // This \r\n is emitted by ScrollFrame to
expectedOutput.push_back("\n"); // add a newline to the bottom of the buffer
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AAAA| (b)
// |_ | (b)
expectedOutput.push_back("\x1b[31;80H"); // Move the cursor BACK to the wrapped row
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AAAA| (b) The cursor is actually on the last A here
// | | (b)
expectedOutput.push_back(std::string(1, 'A')); // Reprint the last character of the wrapped row
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AAAA|_ (w) The cursor is actually on the last A here
// | | (b)
expectedOutput.push_back(std::string(20, 'A')); // Print the second line.
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AAAA| (w)
// |AAAAA_ | (b) There are 20 'A's on this line.
hostSm.ProcessString(std::wstring(wrappedLineLength, L'A'));
auto verifyBuffer = [](const TextBuffer& tb, const short wrappedRow) {
// Buffer contents should look like the following: (80 wide)
// (w) means we hard wrapped the line
// (b) means the line is _not_ wrapped (it's broken, the default state.)
// cursor is on the '_'
//
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AAAA| (w)
// |AAAAA_ ... | (b) (There are 20 'A's on this line.)
VERIFY_IS_TRUE(tb.GetRowByOffset(wrappedRow).GetCharRow().WasWrapForced());
VERIFY_IS_FALSE(tb.GetRowByOffset(wrappedRow + 1).GetCharRow().WasWrapForced());
auto iter0 = tb.GetCellDataAt({ 0, wrappedRow });
TestUtils::VerifySpanOfText(L"A", iter0, 0, TerminalViewWidth);
auto iter1 = tb.GetCellDataAt({ 0, wrappedRow + 1 });
TestUtils::VerifySpanOfText(L"A", iter1, 0, 20);
auto iter2 = tb.GetCellDataAt({ 20, wrappedRow + 1 });
TestUtils::VerifySpanOfText(L" ", iter2, 0, TerminalViewWidth - 20);
};
verifyBuffer(hostTb, hostView.BottomInclusive() - 1);
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb, term->_mutableViewport.BottomInclusive() - 1);
}
void ConptyRoundtripTests::ScrollWithChangesInMiddle()
{
Log::Comment(L"This test checks emitting a wrapped line at the bottom of the"
L" viewport while _also_ emitting other text elsewhere in the same frame. This"
L" output will cause us to scroll the viewport in one frame, but we need to"
L" make sure the wrapped line _stays_ wrapped, and the scrolled text appears in"
L" the right place.");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
// First, fill the buffer with contents, so conpty starts circling
const auto hostView = si.GetViewport();
const auto end = 2 * hostView.Height();
for (auto i = 0; i < end; i++)
{
Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end));
expectedOutput.push_back("X");
if (i < hostView.BottomInclusive())
{
expectedOutput.push_back("\r\n");
}
else
{
// After we hit the bottom of the viewport, the newlines come in
// separated by empty writes for whatever reason.
expectedOutput.push_back("\r");
expectedOutput.push_back("\n");
expectedOutput.push_back("");
}
hostSm.ProcessString(L"X\n");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
const auto wrappedLineLength = TerminalViewWidth + 20;
// In the Terminal, we're going to expect:
expectedOutput.push_back("\x1b[15;1H"); // Move the cursor to row 14, col 0
expectedOutput.push_back("Y"); // Print a 'Y'
expectedOutput.push_back("\x1b[32;1H"); // Move the cursor to the last row
expectedOutput.push_back(std::string(TerminalViewWidth, 'A')); // Print the first 80 'A's
// This is going to be the end of the first frame - b/c we moved the cursor
// in the middle of the frame, we're going to hide/show the cursor during
// this frame
expectedOutput.push_back("\x1b[?25h"); // hide the cursor
// On the subsequent frame:
// TODO GH#5228 might break the "newline & repaint the wrapped char" checks here, that's okay.
expectedOutput.push_back("\r"); // This \r\n is emitted by ScrollFrame to
expectedOutput.push_back("\n"); // add a newline to the bottom of the buffer
expectedOutput.push_back("\x1b[31;80H"); // Move the cursor BACK to the wrapped row
expectedOutput.push_back(std::string(1, 'A')); // Reprint the last character of the wrapped row
expectedOutput.push_back(std::string(20, 'A')); // Print the second line.
_logConpty = true;
// To the host, we'll do something very similar:
hostSm.ProcessString(L"\x1b"
L"7"); // Save cursor
hostSm.ProcessString(L"\x1b[15;1H"); // Move the cursor to row 14, col 0
hostSm.ProcessString(L"Y"); // Print a 'Y'
hostSm.ProcessString(L"\x1b"
L"8"); // Restore
hostSm.ProcessString(std::wstring(wrappedLineLength, L'A')); // Print 100 'A's
auto verifyBuffer = [](const TextBuffer& tb, const til::rectangle viewport) {
const short wrappedRow = viewport.bottom<short>() - 2;
const short start = viewport.top<short>();
for (short i = start; i < wrappedRow; i++)
{
Log::Comment(NoThrowString().Format(L"Checking row %d", i));
TestUtils::VerifyExpectedString(tb, i == start + 13 ? L"Y" : L"X", { 0, i });
}
VERIFY_IS_TRUE(tb.GetRowByOffset(wrappedRow).GetCharRow().WasWrapForced());
VERIFY_IS_FALSE(tb.GetRowByOffset(wrappedRow + 1).GetCharRow().WasWrapForced());
auto iter0 = tb.GetCellDataAt({ 0, wrappedRow });
TestUtils::VerifySpanOfText(L"A", iter0, 0, TerminalViewWidth);
auto iter1 = tb.GetCellDataAt({ 0, wrappedRow + 1 });
TestUtils::VerifySpanOfText(L"A", iter1, 0, 20);
auto iter2 = tb.GetCellDataAt({ 20, wrappedRow + 1 });
TestUtils::VerifySpanOfText(L" ", iter2, 0, TerminalViewWidth - 20);
};
Log::Comment(NoThrowString().Format(L"Checking the host buffer..."));
verifyBuffer(hostTb, hostView.ToInclusive());
Log::Comment(NoThrowString().Format(L"... Done"));
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(NoThrowString().Format(L"Checking the terminal buffer..."));
verifyBuffer(termTb, term->_mutableViewport.ToInclusive());
Log::Comment(NoThrowString().Format(L"... Done"));
}
void ConptyRoundtripTests::ScrollWithMargins()
{
auto& g = ServiceLocator::LocateGlobals();
@@ -1087,6 +1387,7 @@ void ConptyRoundtripTests::ScrollWithMargins()
// The letters represent the data in the TMUX pane.
// The final *** line represents the mode line which we will
// attempt to hold in place and not scroll.
// Note that the last line will contain one '*' less than the width of the window.
Log::Comment(L"Fill host with text pattern by feeding it into VT parser.");
const auto rowsToWrite = initialTermView.Height() - 1;
@@ -1103,7 +1404,7 @@ void ConptyRoundtripTests::ScrollWithMargins()
}
// For the last one, write out the asterisks for the mode line.
for (auto i = 0; i < initialTermView.Width(); ++i)
for (auto i = 0; i < initialTermView.Width() - 1; ++i)
{
hostSm.ProcessCharacter('*');
}
@@ -1127,7 +1428,7 @@ void ConptyRoundtripTests::ScrollWithMargins()
}
// For the last row, verify we have an entire row of asterisks for the mode line.
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
const std::wstring expectedModeLine(initialTermView.Width() - 1, L'*');
const COORD expectedPos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
TestUtils::VerifyExpectedString(tb, expectedModeLine, expectedPos);
};
@@ -1140,13 +1441,8 @@ void ConptyRoundtripTests::ScrollWithMargins()
expectedOutput.push_back("\r\n");
}
{
const std::string expectedString(initialTermView.Width(), '*');
const std::string expectedString(initialTermView.Width() - 1, '*');
expectedOutput.push_back(expectedString);
// Cursor gets reset into bottom right corner as we're writing all the way into that corner.
std::stringstream ss;
ss << "\x1b[" << initialTermView.Height() << ";" << initialTermView.Width() << "H";
expectedOutput.push_back(ss.str());
}
Log::Comment(L"Verify host buffer contains pattern.");
@@ -1257,7 +1553,7 @@ void ConptyRoundtripTests::ScrollWithMargins()
// For the last row, verify we have an entire row of asterisks for the mode line.
{
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
const std::wstring expectedModeLine(initialTermView.Width() - 1, L'*');
const COORD modeLinePos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
TestUtils::VerifyExpectedString(tb, expectedModeLine, modeLinePos);
}
@@ -1279,8 +1575,9 @@ void ConptyRoundtripTests::ScrollWithMargins()
expectedOutput.push_back("\r\n");
}
{
const std::string expectedString(initialTermView.Width(), '*');
expectedOutput.push_back(expectedString);
const std::string expectedString(initialTermView.Width() - 1, '*');
// There will be one extra blank space at the end of the line, to prevent delayed EOL wrapping
expectedOutput.push_back(expectedString + " ");
}
{
// Cursor gets reset into second line from bottom, left most column
@@ -1302,3 +1599,590 @@ void ConptyRoundtripTests::ScrollWithMargins()
// Verify the terminal side.
verifyBufferAfter(termTb);
}
void ConptyRoundtripTests::DontWrapMoveCursorInSingleFrame()
{
// See https://github.com/microsoft/terminal/pull/5181#issuecomment-607427840
Log::Comment(L"This is a test for when a line of text exactly wrapped, but "
L"the cursor didn't end the frame at the end of line (waiting "
L"for more wrapped text). We should still move the cursor in "
L"this case.");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
auto verifyBuffer = [](const TextBuffer& tb) {
// Simple verification: Make sure the cursor is in the correct place,
// and that it's visible. We don't care so much about the buffer
// contents in this test.
const COORD expectedCursor{ 8, 3 };
VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition());
VERIFY_IS_TRUE(tb.GetCursor().IsVisible());
};
hostSm.ProcessString(L"\x1b[?25l");
hostSm.ProcessString(L"\x1b[H");
hostSm.ProcessString(L"\x1b[75C");
hostSm.ProcessString(L"XXXXX");
hostSm.ProcessString(L"\x1b[4;9H");
hostSm.ProcessString(L"\x1b[?25h");
Log::Comment(L"Checking the host buffer state");
verifyBuffer(hostTb);
expectedOutput.push_back("\x1b[75C");
expectedOutput.push_back("XXXXX");
expectedOutput.push_back("\x1b[4;9H");
// We're _not_ expecting a cursor on here, because we didn't actually hide
// the cursor during the course of this frame
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"Checking the terminal buffer state");
verifyBuffer(termTb);
}
void ConptyRoundtripTests::ClearHostTrickeryTest()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:paintEachNewline", L"{0, 1, 2}")
TEST_METHOD_PROPERTY(L"Data:cursorOnNextLine", L"{false, true}")
TEST_METHOD_PROPERTY(L"Data:paintAfterDECALN", L"{false, true}")
TEST_METHOD_PROPERTY(L"Data:changeAttributes", L"{false, true}")
TEST_METHOD_PROPERTY(L"Data:useLongSpaces", L"{false, true}")
TEST_METHOD_PROPERTY(L"Data:printTextAfterSpaces", L"{false, true}")
END_TEST_METHOD_PROPERTIES();
constexpr int PaintEveryNewline = 0;
constexpr int PaintAfterAllNewlines = 1;
constexpr int DontPaintAfterNewlines = 2;
INIT_TEST_PROPERTY(int, paintEachNewline, L"Any of: manually PaintFrame after each newline is emitted, once at the end of all newlines, or not at all");
INIT_TEST_PROPERTY(bool, cursorOnNextLine, L"Either leave the cursor on the first line, or place it on the second line of the buffer");
INIT_TEST_PROPERTY(bool, paintAfterDECALN, L"Controls whether we manually paint a frame after the DECALN sequence is emitted.");
INIT_TEST_PROPERTY(bool, changeAttributes, L"If true, change the text attributes after the 'A's and spaces");
INIT_TEST_PROPERTY(bool, useLongSpaces, L"If true, print 10 spaces instead of 5, longer than a CUF sequence.");
INIT_TEST_PROPERTY(bool, printTextAfterSpaces, L"If true, print \"ZZZZZ\" after the spaces on the first line.");
// See https://github.com/microsoft/terminal/issues/5039#issuecomment-606833841
Log::Comment(L"This is a more than comprehensive test for GH#5039. We're "
L"going to print some text to the buffer, then fill the alt-"
L"buffer with text, then switch back to the main buffer. The "
L"text from the alt buffer should not pollute the main buffer.");
// The text we're printing will look like one of the following, with the
// cursor on the _
// * cursorOnNextLine=false, useLongSpaces=false:
// AAAAA ZZZZZ_
// * cursorOnNextLine=false, useLongSpaces=true:
// AAAAA ZZZZZ_
// * cursorOnNextLine=true, useLongSpaces=false:
// AAAAA ZZZZZ
// BBBBB_
// * cursorOnNextLine=true, useLongSpaces=true:
// AAAAA ZZZZZ
// BBBBB_
//
// If printTextAfterSpaces=false, then we won't print the "ZZZZZ"
//
// The interesting case that repros the bug in GH#5039 is
// - paintEachNewline=DontPaintAfterNewlines (2)
// - cursorOnNextLine=false
// - paintAfterDECALN=<any>
// - changeAttributes=true
// - useLongSpaces=<any>
// - printTextAfterSpaces=<any>
//
// All the possible cases are left here though, to catch potential future regressions.
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
auto verifyBuffer = [&cursorOnNextLine, &useLongSpaces, &printTextAfterSpaces](const TextBuffer& tb,
const til::rectangle viewport) {
// We _would_ expect the Terminal's cursor to be on { 8, 0 }, but this
// is currently broken due to #381/#4676. So we'll use the viewport
// provided to find the actual Y position of the cursor.
const short viewTop = viewport.origin().y<short>();
const short cursorRow = viewTop + (cursorOnNextLine ? 1 : 0);
const short cursorCol = (cursorOnNextLine ? 5 :
(10 + (useLongSpaces ? 5 : 0) + (printTextAfterSpaces ? 5 : 0)));
const COORD expectedCursor{ cursorCol, cursorRow };
VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition());
VERIFY_IS_TRUE(tb.GetCursor().IsVisible());
auto iter = TestUtils::VerifyExpectedString(tb, L"AAAAA", { 0, viewTop });
TestUtils::VerifyExpectedString(useLongSpaces ? L" " : L" ", iter);
if (printTextAfterSpaces)
{
TestUtils::VerifyExpectedString(L"ZZZZZ", iter);
}
else
{
TestUtils::VerifyExpectedString(L" ", iter);
}
TestUtils::VerifyExpectedString(L" ", iter);
if (cursorOnNextLine)
{
TestUtils::VerifyExpectedString(tb, L"BBBBB", { 0, cursorRow });
}
};
// We're _not_ checking the conpty output during this test, only the side effects.
_checkConptyOutput = false;
gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state.
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
Log::Comment(L"Setting up the host buffer...");
hostSm.ProcessString(L"AAAAA");
hostSm.ProcessString(useLongSpaces ? L" " : L" ");
if (changeAttributes)
{
hostSm.ProcessString(L"\x1b[44m");
}
if (printTextAfterSpaces)
{
hostSm.ProcessString(L"ZZZZZ");
}
hostSm.ProcessString(L"\x1b[0m");
if (cursorOnNextLine)
{
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"BBBBB");
}
Log::Comment(L"Painting after the initial setup.");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"Switching to the alt buffer and using DECALN to fill it with 'E's");
hostSm.ProcessString(L"\x1b[?1049h");
hostSm.ProcessString(L"\x1b#8");
if (paintAfterDECALN)
{
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
for (auto i = 0; i < si.GetViewport().Height(); i++)
{
hostSm.ProcessString(L"\n");
if (paintEachNewline == PaintEveryNewline)
{
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
}
if (paintEachNewline == PaintAfterAllNewlines)
{
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
Log::Comment(L"Returning to the main buffer.");
hostSm.ProcessString(L"\x1b[?1049l");
Log::Comment(L"Checking the host buffer state");
verifyBuffer(hostTb, si.GetViewport().ToInclusive());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"Checking the terminal buffer state");
verifyBuffer(termTb, term->_mutableViewport.ToInclusive());
}
void ConptyRoundtripTests::OverstrikeAtBottomOfBuffer()
{
// See https://github.com/microsoft/terminal/pull/5181#issuecomment-607545241
Log::Comment(L"This test replicates the zsh menu-complete functionality. In"
L" the course of a single frame, we're going to both scroll "
L"the frame and print multiple lines of text above the bottom line.");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
auto verifyBuffer = [](const TextBuffer& tb,
const til::rectangle viewport) {
const auto lastRow = viewport.bottom<short>() - 1;
const til::point expectedCursor{ 0, lastRow - 1 };
VERIFY_ARE_EQUAL(expectedCursor, til::point{ tb.GetCursor().GetPosition() });
VERIFY_IS_TRUE(tb.GetCursor().IsVisible());
TestUtils::VerifyExpectedString(tb, L"AAAAAAAAAA DDDDDDDDDD", til::point{ 0, lastRow - 2 });
TestUtils::VerifyExpectedString(tb, L"BBBBBBBBBB", til::point{ 0, lastRow - 1 });
TestUtils::VerifyExpectedString(tb, L"FFFFFFFFFE", til::point{ 0, lastRow });
};
_logConpty = true;
// We're _not_ checking the conpty output during this test, only the side effects.
_checkConptyOutput = false;
hostSm.ProcessString(L"\x1b#8");
hostSm.ProcessString(L"\x1b[32;1H");
hostSm.ProcessString(L"\x1b[J");
hostSm.ProcessString(L"AAAAAAAAAA");
hostSm.ProcessString(L"\x1b[K");
hostSm.ProcessString(L"\r");
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"BBBBBBBBBB");
hostSm.ProcessString(L"\x1b[K");
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"CCCCCCCCCC");
hostSm.ProcessString(L"\x1b[2A");
hostSm.ProcessString(L"\r");
hostSm.ProcessString(L"\x1b[20C");
hostSm.ProcessString(L"DDDDDDDDDD");
hostSm.ProcessString(L"\x1b[K");
hostSm.ProcessString(L"\r");
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"\x1b[1B");
hostSm.ProcessString(L"EEEEEEEEEE");
hostSm.ProcessString(L"\r");
hostSm.ProcessString(L"FFFFFFFFF");
hostSm.ProcessString(L"\r");
hostSm.ProcessString(L"\x1b[A");
hostSm.ProcessString(L"\x1b[A");
hostSm.ProcessString(L"\n");
Log::Comment(L"========== Checking the host buffer state ==========");
verifyBuffer(hostTb, si.GetViewport().ToInclusive());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state ==========");
verifyBuffer(termTb, term->_mutableViewport.ToInclusive());
}
void ConptyRoundtripTests::MarginsWithStatusLine()
{
// See https://github.com/microsoft/terminal/issues/5161
//
// This test reproduces a case from the MSYS/cygwin (runtime < 3.1) vim.
// From what I can tell, they implement scrolling by emitting a newline at
// the bottom of the buffer (to create a new blank line), then they use
// ScrollConsoleScreenBuffer to shift the status line(s) down a line, and
// then they re-printing the status line.
Log::Comment(L"Newline, and scroll the bottom lines of the buffer down with"
L" ScrollConsoleScreenBuffer to emulate how cygwin VIM works");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
auto verifyBuffer = [](const TextBuffer& tb,
const til::rectangle viewport) {
const auto lastRow = viewport.bottom<short>() - 1;
const til::point expectedCursor{ 1, lastRow };
VERIFY_ARE_EQUAL(expectedCursor, til::point{ tb.GetCursor().GetPosition() });
VERIFY_IS_TRUE(tb.GetCursor().IsVisible());
TestUtils::VerifyExpectedString(tb, L"EEEEEEEEEE", til::point{ 0, lastRow - 4 });
TestUtils::VerifyExpectedString(tb, L"AAAAAAAAAA", til::point{ 0, lastRow - 3 });
TestUtils::VerifyExpectedString(tb, L" ", til::point{ 0, lastRow - 2 });
TestUtils::VerifyExpectedString(tb, L"XBBBBBBBBB", til::point{ 0, lastRow - 1 });
TestUtils::VerifyExpectedString(tb, L"YCCCCCCCCC", til::point{ 0, lastRow });
};
// We're _not_ checking the conpty output during this test, only the side effects.
_checkConptyOutput = false;
// Use DECALN to fill the buffer with 'E's.
hostSm.ProcessString(L"\x1b#8");
const short originalBottom = si.GetViewport().BottomInclusive();
// Print 3 lines into the bottom of the buffer:
// AAAAAAAAAA
// BBBBBBBBBB
// CCCCCCCCCC
// In this test, the 'B' and 'C' lines represent the status lines at the
// bottom of vim, and the 'A' line is a buffer line.
hostSm.ProcessString(L"\x1b[30;1H");
hostSm.ProcessString(L"AAAAAAAAAA");
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"BBBBBBBBBB");
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"CCCCCCCCCC");
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
// After printing the 'C' line, the cursor is on the bottom line of the viewport.
// Emit a newline here to get a new line at the bottom of the viewport.
hostSm.ProcessString(L"\n");
const short newBottom = si.GetViewport().BottomInclusive();
{
// Emulate calling ScrollConsoleScreenBuffer to scroll the B and C lines
// down one line.
SMALL_RECT src;
src.Top = newBottom - 2;
src.Left = 0;
src.Right = si.GetViewport().Width();
src.Bottom = originalBottom;
COORD tgt = { 0, newBottom - 1 };
TextAttribute useThisAttr(0x07); // We don't terribly care about the attributes so this is arbitrary
ScrollRegion(si, src, std::nullopt, tgt, L' ', useThisAttr);
}
// Move the cursor to the location of the B line
hostSm.ProcessString(L"\x1b[31;1H");
// Print an 'X' on the 'B' line, and a 'Y' on the 'C' line.
hostSm.ProcessString(L"X");
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"Y");
Log::Comment(L"========== Checking the host buffer state ==========");
verifyBuffer(hostTb, si.GetViewport().ToInclusive());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state ==========");
verifyBuffer(termTb, term->_mutableViewport.ToInclusive());
}
void ConptyRoundtripTests::OutputWrappedLineWithSpace()
{
// See https://github.com/microsoft/terminal/pull/5181#issuecomment-610110348
Log::Comment(L"Ensures that a buffer line in conhost that wrapped _on a "
L"space_ will still be emitted as wrapped.");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
const auto firstTextLength = TerminalViewWidth - 2;
const auto spacesLength = 3;
const auto secondTextLength = 1;
sm.ProcessString(std::wstring(firstTextLength, L'A'));
sm.ProcessString(std::wstring(spacesLength, L' '));
sm.ProcessString(std::wstring(secondTextLength, L'B'));
auto verifyBuffer = [&](const TextBuffer& tb) {
// Buffer contents should look like the following: (80 wide)
// (w) means we hard wrapped the line
// (b) means the line is _not_ wrapped (it's broken, the default state.)
//
// |AAAA...AA | (w)
// | B_ ... | (b) (cursor is on the '_')
// | ... | (b)
VERIFY_IS_TRUE(tb.GetRowByOffset(0).GetCharRow().WasWrapForced());
VERIFY_IS_FALSE(tb.GetRowByOffset(1).GetCharRow().WasWrapForced());
// First row
auto iter0 = tb.GetCellDataAt({ 0, 0 });
TestUtils::VerifySpanOfText(L"A", iter0, 0, firstTextLength);
TestUtils::VerifySpanOfText(L" ", iter0, 0, 2);
// Second row
auto iter1 = tb.GetCellDataAt({ 0, 1 });
TestUtils::VerifySpanOfText(L" ", iter1, 0, 1);
auto iter2 = tb.GetCellDataAt({ 1, 1 });
TestUtils::VerifySpanOfText(L"B", iter2, 0, secondTextLength);
};
Log::Comment(L"========== Checking the host buffer state ==========");
verifyBuffer(hostTb);
std::string firstLine = std::string(firstTextLength, 'A');
firstLine += " ";
std::string secondLine{ " B" };
expectedOutput.push_back(firstLine);
expectedOutput.push_back(secondLine);
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state ==========");
verifyBuffer(termTb);
}
void ConptyRoundtripTests::OutputWrappedLineWithSpaceAtBottomOfBuffer()
{
// See https://github.com/microsoft/terminal/pull/5181#issuecomment-610110348
// This is the same test as OutputWrappedLineWithSpace, but at the bottom of
// the buffer, so we get scrolling behavior as well.
Log::Comment(L"Ensures that a buffer line in conhost that wrapped _on a "
L"space_ will still be emitted as wrapped.");
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
// First, fill the buffer with contents, so conpty starts circling
const auto hostView = si.GetViewport();
const auto end = 2 * hostView.Height();
for (auto i = 0; i < end; i++)
{
Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end));
expectedOutput.push_back("X");
if (i < hostView.BottomInclusive())
{
expectedOutput.push_back("\r\n");
}
else
{
// After we hit the bottom of the viewport, the newlines come in
// separated by empty writes for whatever reason.
expectedOutput.push_back("\r");
expectedOutput.push_back("\n");
expectedOutput.push_back("");
}
sm.ProcessString(L"X\n");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
const auto firstTextLength = TerminalViewWidth - 2;
const auto spacesLength = 3;
const auto secondTextLength = 1;
std::string firstLine = std::string(firstTextLength, 'A');
firstLine += " ";
std::string secondLine{ " B" };
// The following diagrams show the buffer contents after each string emitted
// from conpty. For each of these diagrams:
// (w) means we hard wrapped the line
// (b) means the line is _not_ wrapped (it's broken, the default state.)
// cursor is on the '_'
// Initial state:
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |_ | (b)
expectedOutput.push_back(firstLine);
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AA _| (w) The cursor is actually on the last ' ' here
// TODO GH#5228 might break the "newline & repaint the wrapped char" checks here, that's okay.
expectedOutput.push_back("\r"); // This \r\n is emitted by ScrollFrame to
expectedOutput.push_back("\n"); // add a newline to the bottom of the buffer
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AA | (b)
// |_ | (b)
expectedOutput.push_back("\x1b[31;80H"); // Move the cursor BACK to the wrapped row
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AA _| (b) The cursor is actually on the last ' ' here
// | | (b)
expectedOutput.push_back(std::string(1, ' ')); // Reprint the last character of the wrapped row
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AA |_ (w) The cursor is actually on the last ' ' here
// | | (b)
expectedOutput.push_back(secondLine);
// |X | (b)
// |X | (b)
// ...
// |X | (b)
// |AAAAAAAA...AA | (w)
// | B_ | (b)
sm.ProcessString(std::wstring(firstTextLength, L'A'));
sm.ProcessString(std::wstring(spacesLength, L' '));
sm.ProcessString(std::wstring(secondTextLength, L'B'));
auto verifyBuffer = [&](const TextBuffer& tb, const til::rectangle viewport) {
// Buffer contents should look like the following: (80 wide)
// (w) means we hard wrapped the line
// (b) means the line is _not_ wrapped (it's broken, the default state.)
//
// |AAAA...AA | (w)
// | B_ ... | (b) (cursor is on the '_')
// | ... | (b)
const short wrappedRow = viewport.bottom<short>() - 2;
VERIFY_IS_TRUE(tb.GetRowByOffset(wrappedRow).GetCharRow().WasWrapForced());
VERIFY_IS_FALSE(tb.GetRowByOffset(wrappedRow + 1).GetCharRow().WasWrapForced());
// First row
auto iter0 = tb.GetCellDataAt({ 0, wrappedRow });
TestUtils::VerifySpanOfText(L"A", iter0, 0, firstTextLength);
TestUtils::VerifySpanOfText(L" ", iter0, 0, 2);
// Second row
auto iter1 = tb.GetCellDataAt({ 0, wrappedRow + 1 });
TestUtils::VerifySpanOfText(L" ", iter1, 0, 1);
auto iter2 = tb.GetCellDataAt({ 1, wrappedRow + 1 });
TestUtils::VerifySpanOfText(L"B", iter2, 0, secondTextLength);
};
Log::Comment(L"========== Checking the host buffer state ==========");
verifyBuffer(hostTb, hostView.ToInclusive());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state ==========");
verifyBuffer(termTb, term->_mutableViewport.ToInclusive());
}

View File

@@ -48,12 +48,12 @@ namespace TerminalCoreUnitTests
// Verify that Alt+a generates a lowercase a on the input
expectedinput = L"\x1b"
"a";
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', 0, ControlKeyStates::LeftAltPressed));
VERIFY_IS_TRUE(term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed));
// Verify that Alt+shift+a generates a uppercase a on the input
expectedinput = L"\x1b"
"A";
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed));
VERIFY_IS_TRUE(term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed));
}
void InputTest::AltSpace()
@@ -62,5 +62,6 @@ namespace TerminalCoreUnitTests
// bring up the system menu for restore, min/maximize, size, move,
// close
VERIFY_IS_FALSE(term.SendKeyEvent(L' ', 0, ControlKeyStates::LeftAltPressed));
VERIFY_IS_FALSE(term.SendCharEvent(L' ', 0, ControlKeyStates::LeftAltPressed));
}
}

View File

@@ -51,7 +51,7 @@ AppHost::AppHost() noexcept :
_logic,
std::placeholders::_1,
std::placeholders::_2));
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_window->MakeWindow();
}
@@ -338,3 +338,48 @@ void AppHost::_ToggleFullscreen(const winrt::Windows::Foundation::IInspectable&,
{
_window->ToggleFullscreen();
}
// Method Description:
// - Called when the IslandWindow has received a WM_MOUSEWHEEL message. This can
// happen on some laptops, where their trackpads won't scroll inactive windows
// _ever_.
// - We're going to take that message and manually plumb it through to our
// TermControl's, or anything else that implements IMouseWheelListener.
// - See GH#979 for more details.
// Arguments:
// - coord: The Window-relative, logical coordinates location of the mouse during this event.
// - delta: the wheel delta that triggered this event.
// Return Value:
// - <none>
void AppHost::_WindowMouseWheeled(const til::point coord, const int32_t delta)
{
if (_logic)
{
// Find all the elements that are underneath the mouse
auto elems = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::FindElementsInHostCoordinates(coord, _logic.GetRoot());
for (const auto& e : elems)
{
// If that element has implemented IMouseWheelListener, call OnMouseWheel on that element.
if (auto control{ e.try_as<winrt::Microsoft::Terminal::TerminalControl::IMouseWheelListener>() })
{
try
{
// Translate the event to the coordinate space of the control
// we're attempting to dispatch it to
const auto transform = e.TransformToVisual(nullptr);
const til::point controlOrigin{ til::math::flooring, transform.TransformPoint(til::point{ 0, 0 }) };
const til::point offsetPoint = coord - controlOrigin;
if (control.OnMouseWheel(offsetPoint, delta))
{
// If the element handled the mouse wheel event, don't
// continue to iterate over the remaining controls.
break;
}
}
CATCH_LOG();
}
}
}
}

View File

@@ -35,4 +35,5 @@ private:
const winrt::Windows::UI::Xaml::ElementTheme& arg);
void _ToggleFullscreen(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::TerminalApp::ToggleFullscreenEventArgs& arg);
void _WindowMouseWheeled(const til::point coord, const int32_t delta);
};

View File

@@ -326,6 +326,40 @@ void IslandWindow::OnSize(const UINT width, const UINT height)
_windowCloseButtonClickedHandler();
return 0;
}
case WM_MOUSEWHEEL:
try
{
// This whole handler is a hack for GH#979.
//
// On some laptops, their trackpads won't scroll inactive windows
// _ever_. With our entire window just being one giant XAML Island, the
// touchpad driver thinks our entire window is inactive, and won't
// scroll the XAML island. On those types of laptops, we'll get a
// WM_MOUSEWHEEL here, in our root window, when the trackpad scrolls.
// We're going to take that message and manually plumb it through to our
// TermControl's, or anything else that implements IMouseWheelListener.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx
// Important! Do not use the LOWORD or HIWORD macros to extract the x-
// and y- coordinates of the cursor position because these macros return
// incorrect results on systems with multiple monitors. Systems with
// multiple monitors can have negative x- and y- coordinates, and LOWORD
// and HIWORD treat the coordinates as unsigned quantities.
const til::point eventPoint{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
// This mouse event is relative to the display origin, not the window. Convert here.
const til::rectangle windowRect{ GetWindowRect() };
const auto origin = windowRect.origin();
const auto relative = eventPoint - origin;
// Convert to logical scaling before raising the event.
const auto real = relative / GetCurrentDpiScale();
const short wheelDelta = static_cast<short>(HIWORD(wparam));
// Raise an event, so any listeners can handle the mouse wheel event manually.
_MouseScrolledHandlers(real, wheelDelta);
return 0;
}
CATCH_LOG();
}
// TODO: handle messages here...

View File

@@ -72,6 +72,7 @@ public:
DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);
WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
protected:
void ForceResize()

View File

@@ -366,7 +366,9 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept
// GH#1438 - Attempt to detect if there's an autohide taskbar, and if there
// is, reduce our size a bit on the side with the taskbar, so the user can
// still mouse-over the taskbar to reveal it.
HMONITOR hMon = MonitorFromWindow(_window.get(), MONITOR_DEFAULTTONULL);
// GH#5209 - make sure to use MONITOR_DEFAULTTONEAREST, so that this will
// still find the right monitor even when we're restoring from minimized.
HMONITOR hMon = MonitorFromWindow(_window.get(), MONITOR_DEFAULTTONEAREST);
if (hMon && (_isMaximized || _fullscreen))
{
MONITORINFO monInfo{ 0 };

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