Compare commits

..

69 Commits

Author SHA1 Message Date
Dustin L. Howett
918c376fca Migrate spelling-0.0.21 changes from main 2022-11-11 16:09:48 -06:00
Mike Griese
8d8bfca91b Merge branch 'main' into dev/migrie/fhl/more-shell-integration 2022-11-11 16:09:48 -06:00
Mike Griese
06427c3965 I don't think that was supposed to be there 2022-09-20 05:18:04 -05:00
Mike Griese
58f7f0e836 I was gonna start launching a process to get suggestions too, but meh 2022-09-19 16:16:23 -05:00
Mike Griese
0c68bd1778 notes 2022-09-19 14:24:46 -05:00
Mike Griese
4def21a018 Automatic parsing of .wt.json files in the CWD, for additional actions 2022-09-19 14:20:32 -05:00
Mike Griese
1e357d0694 Now with a more sensible struct for passing around data 2022-09-19 10:52:40 -05:00
Mike Griese
f474c4bacb this is a bebtter algo 2022-09-19 10:05:31 -05:00
Mike Griese
0a76608530 plumb command history through task view too 2022-09-19 09:26:17 -05:00
Mike Griese
d0c4c29693 refactor to just use a single action, tasks{source:(prompt)} 2022-09-19 07:08:06 -05:00
Mike Griese
99f146635d now with better shell integration for cmd 2022-09-16 15:30:33 -05:00
Mike Griese
c133e2f093 cleaner 2022-09-16 15:21:26 -05:00
Mike Griese
56cfeb44b0 that's all of them, I think 2022-09-16 15:13:09 -05:00
Mike Griese
31f57aec8d in retrospect I bet we can revert this 2022-09-16 12:05:38 -05:00
Mike Griese
d12c6dfc1d this plumbs through FTCS_COMMAND_START, a little jank tho 2022-09-16 12:05:23 -05:00
Mike Griese
3103371722 that's a decent hack 2022-09-15 17:01:35 -05:00
Mike Griese
c9a474ddb0 this is the dialog 2022-09-15 16:39:42 -05:00
Mike Griese
1cde67ac46 I'm amazing 2022-09-15 14:50:09 -05:00
Mike Griese
e633a05890 all the plumbing, no implementation 2022-09-15 12:55:56 -05:00
Mike Griese
6f5b9fba6c XAML has no limit to how wack it can be 2022-09-15 12:31:25 -05:00
Mike Griese
86f4ca0f43 Lemme tell you, TeachingTip is wack 2022-09-15 12:13:00 -05:00
Mike Griese
ac3af4db5c this is... wacky 2022-09-15 11:56:45 -05:00
Mike Griese
73f172a1cc One shot, it's done 2022-09-15 10:26:05 -05:00
Mike Griese
0f326920de Merge branch 'dev/migrie/fhl/vscode-autocomplete-prototype' into dev/migrie/fhl/tasks-view-2022 2022-09-15 09:21:00 -05:00
Mike Griese
ba25f33d90 lol so don't throttle this you idiot 2022-07-21 12:41:38 -05:00
Mike Griese
01a64b1b30 this is a fun hack 2022-07-20 12:46:19 -05:00
Mike Griese
0141f68aa8 some cleanup 2022-07-20 10:49:44 -05:00
Mike Griese
9000a7fd3c the replacement index needs to be used to backspace the old text, and type the new text 2022-07-20 10:35:43 -05:00
Mike Griese
b11055907e plumbing works, parsing works 2022-07-20 09:44:09 -05:00
Mike Griese
4d4c75fa1c change the plumbing for the sake of prototyping 2022-07-20 09:07:42 -05:00
Mike Griese
a96e2e6bd2 Merge branch 'dev/migrie/fhl/menu-complete-prototype' into dev/migrie/fhl/vscode-autocomplete-prototype
# Conflicts:
#	src/cascadia/TerminalApp/ActionPreviewHandlers.cpp
#	src/cascadia/TerminalApp/CommandPalette.xaml
#	src/cascadia/TerminalApp/TerminalPage.cpp
#	src/cascadia/TerminalApp/TerminalPage.h
#	src/cascadia/TerminalControl/ControlCore.cpp
#	src/cascadia/TerminalControl/ControlCore.h
#	src/cascadia/TerminalControl/ControlCore.idl
#	src/cascadia/TerminalControl/ICoreState.idl
#	src/cascadia/TerminalControl/TermControl.cpp
#	src/cascadia/TerminalControl/TermControl.h
#	src/cascadia/TerminalControl/TermControl.idl
#	src/cascadia/TerminalCore/ITerminalApi.hpp
#	src/cascadia/TerminalCore/Terminal.cpp
#	src/cascadia/TerminalCore/Terminal.hpp
#	src/cascadia/TerminalCore/TerminalApi.cpp
#	src/cascadia/TerminalCore/TerminalDispatch.cpp
#	src/cascadia/TerminalCore/TerminalDispatch.hpp
#	src/cascadia/TerminalCore/TerminalSelection.cpp
#	src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
#	src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp
#	src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
#	src/host/VtIo.cpp
#	src/host/screenInfo.cpp
#	src/host/screenInfo.hpp
#	src/renderer/base/renderer.cpp
#	src/renderer/vt/vtrenderer.hpp
#	src/terminal/adapter/DispatchTypes.hpp
#	src/terminal/adapter/adaptDispatch.hpp
#	src/terminal/parser/OutputStateMachineEngine.cpp
#	src/terminal/parser/OutputStateMachineEngine.hpp
2022-07-20 06:34:35 -05:00
Mike Griese
84d68f082e bugfixes for the demo 2022-04-01 06:55:32 -05:00
Mike Griese
41796c2409 Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/menu-complete-prototype 2022-04-01 05:49:12 -05:00
Mike Griese
9eb77cf0f1 acrylic because this is a TRANSIENT surface 2022-03-23 12:55:34 -05:00
Mike Griese
9cbb172323 resize down when there are few entries 2022-03-23 11:56:27 -05:00
Mike Griese
6e2f53b025 populates menu before opening 2022-03-23 11:45:04 -05:00
Mike Griese
d1afa2af14 relative positioning of the menu 2022-03-22 16:59:27 -05:00
Mike Griese
e52549ace0 Man okay so popups just work instantly, that's awesome 2022-03-22 16:24:43 -05:00
Mike Griese
60f63b09cc Preview the input via the TSF input control. This is awesome, and should go into main 2022-03-22 06:00:06 -05:00
Mike Griese
64c2e856cc This is an e2e prototype for #3121. No shell integration, but works more or less. 2022-03-21 15:21:19 -05:00
Mike Griese
bfebcf248d thru to the command palette 2022-03-21 12:26:00 -05:00
Mike Griese
4d1570f4b4 bubble up thru TermControl 2022-03-21 11:00:22 -05:00
Mike Griese
3a48e19b1d this is a noticably dumber implementation but whatever 2022-03-21 10:25:04 -05:00
Mike Griese
cb81e61949 I thought this was so clever and then I remembered that you can't use any control chars in OSC strings. Fuck that 2022-03-21 10:14:18 -05:00
Mike Griese
e3181e76c9 Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/menu-complete-prototype 2022-03-21 08:26:29 -05:00
Mike Griese
0163878d23 cleanup 2022-03-18 08:26:49 -05:00
Mike Griese
c8f0a5e5d5 Merge branch 'dev/migrie/b/alt-buffer-terminal' into dev/migrie/b/3493-no-wrap-alt-buffer 2022-03-17 15:59:26 -05:00
Mike Griese
65b457113d notes from j4james 2022-03-17 14:32:03 -05:00
Mike Griese
7237fced16 thats not a word either 2022-03-17 14:23:20 -05:00
Mike Griese
bf3d79e41a fine that's not a word 2022-03-16 10:37:05 -05:00
Mike Griese
0cfb4637e2 Merge remote-tracking branch 'origin/main' into dev/migrie/b/alt-buffer-terminal 2022-03-16 10:25:02 -05:00
Mike Griese
4c96fc08a1 Merge branch 'dev/migrie/b/alt-buffer-terminal' into dev/migrie/b/3493-no-wrap-alt-buffer 2022-03-08 17:01:22 -06:00
Mike Griese
e658431c11 Merge remote-tracking branch 'origin/main' into dev/migrie/b/alt-buffer-terminal 2022-03-08 16:57:07 -06:00
Mike Griese
9453aa5ee1 More test cleanup. Make sure viewport doesnt move 2022-03-08 16:54:29 -06:00
Mike Griese
0a1ed70153 I thought this was a test for https://github.com/microsoft/terminal/pull/12561#discussion_r814337255, but I think that "just worked" because conpty magic. 2022-03-08 16:49:22 -06:00
Mike Griese
69d0973f14 make this test way more elaborate 2022-03-08 16:20:29 -06:00
Mike Griese
57094b7d98 start writing tests 2022-03-08 15:56:49 -06:00
Mike Griese
54dc30411c Stashing this for now. I think this does the save/restore cursor stuff that we're supposed to do when entering/exiting the alt buffer 2022-02-28 12:19:50 -06:00
Mike Griese
5d8c0d0b9b comments mostly 2022-02-25 06:01:30 -06:00
Mike Griese
65295796f2 deferred resizing both in the Terminal and the ConPTY 2022-02-25 05:48:34 -06:00
Mike Griese
d08d21626f defer main buffer resizes till we exit, to try and prevent flashing in conpty. That didn't work, unfortunately 2022-02-25 04:22:37 -06:00
Mike Griese
57280d8961 I think this does the whole thing 2022-02-24 16:00:45 -06:00
Mike Griese
60d2c2e26d fix tests 2022-02-24 12:33:20 -06:00
Mike Griese
dff1b94016 spel 2022-02-24 11:45:52 -06:00
Mike Griese
bf24cdd4b0 more comments are always good 2022-02-23 16:14:36 -06:00
Mike Griese
1209fa40c3 one last comment 2022-02-23 16:10:07 -06:00
Mike Griese
dd702c769d Most of the remaining todos, comments 2022-02-23 16:01:09 -06:00
Mike Griese
e94e08c303 This quite nearly implements everything for the Terminal half
Resizing is surely boned but this is 1000% percent better than nothing at all.
2022-02-23 15:29:59 -06:00
Mike Griese
83466a4381 I think this is the conhost half of the changes 2022-02-23 11:20:36 -06:00
447 changed files with 8772 additions and 12020 deletions

View File

@@ -1,15 +0,0 @@
# Commits mentioned in this file will automatically
# be skipped by GitHub's blame view.
# To use it with "git", run
# > git blame --ignore-revs-file ./.git-blame-ignore-revs
# Reformatted the entire codebase
9b92986b49bed8cc41fde4d6ef080921c41e6d9e
# Line Endings changes
cb7a76d96c92aa9fc7b03f69148fb0c75dff191d
5bbf61af8c8f12e6c05d07a696bf7d411b330a67
d07546a6fef73fa4e1fb1c2f01535843d1fcc212
# UTF-8 BOM changes
ddae2a1d49d604487d3c963e5eacbeb73861d986

View File

@@ -14,7 +14,7 @@ body:
label: Windows Terminal version
placeholder: "1.7.3651.0"
description: |
You can copy the version number from the About dialog. Open the About dialog by opening the menu with the "V" button (to the right of the "+" button that opens a new tab) and choosing About from the end of the list.
You can find the version in the about dialog, or by running `wt -v` at the commandline (for the Stable/Preview version youre reporting a bug about).
validations:
required: false

View File

@@ -1,14 +1,20 @@
<!-- 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
## References and Relevant Issues
<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? -->
## References
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #xxx
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
## PR Checklist
- [ ] Closes #xxx
- [ ] Tests added/passed
- [ ] Documentation updated
- If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
- [ ] Schema updated (if necessary)

View File

@@ -2,7 +2,6 @@ admins
allcolors
Apc
apc
backpressure
breadcrumb
breadcrumbs
bsd
@@ -13,23 +12,18 @@ clickable
clig
CMMI
copyable
CtrlDToClose
cybersecurity
dalet
Dcs
dcs
deselection
dialytika
diffing
dje
downside
downsides
dze
dzhe
DTo
EDDB
EDDC
Emacspeak
Enum'd
Fitt
formattings
@@ -66,7 +60,6 @@ lol
lorem
Lorigin
maxed
megathread
minimalistic
mkmk
mnt
@@ -92,7 +85,6 @@ shcha
slnt
Sos
ssh
stakeholders
timeline
timelines
timestamped

View File

@@ -56,7 +56,6 @@ QWORD
regedit
robocopy
SACLs
segoe
sdkddkver
Shobjidl
Skype

View File

@@ -23,7 +23,6 @@ Griese
Hernan
Howett
Illhardt
Imms
iquilezles
italo
jantari
@@ -35,7 +34,6 @@ KODELIFE
Kodelife
Kourosh
kowalczyk
leonardder
leonmsft
Lepilleur
lhecker
@@ -79,18 +77,15 @@ sonpham
stakx
talo
thereses
Thysell
Walisch
WDX
Wellons
Westerman
Wirt
Wojciech
zadjii
Zamor
Zamora
zamora
zljubisic
Zoey
zorio
Zverovich

View File

@@ -61,6 +61,7 @@ argb
ARRAYSIZE
ARROWKEYS
asan
ASBRST
ASBSET
ASDF
asdfghjkl
@@ -251,7 +252,6 @@ conattrs
conbufferout
concfg
conclnt
concretizations
conddkrefs
condrv
conechokey
@@ -410,24 +410,20 @@ DECAWM
DECBKM
DECCARA
DECCKM
DECCKSR
DECCOLM
DECCRA
DECCTR
DECDHL
decdld
DECDMAC
DECDWL
DECEKBD
DECERA
DECFRA
DECID
DECINVM
DECKPAM
DECKPM
DECKPNM
DECLRMM
DECMSR
DECNKM
DECNRCM
DECOM
@@ -437,7 +433,6 @@ DECRARA
DECRC
DECREQTPARM
DECRLM
DECRPM
DECRQM
DECRQSS
DECRQTSR
@@ -458,7 +453,6 @@ DECSLRM
DECSMKR
DECSR
DECSTBM
DECSTGLT
DECSTR
DECSWL
DECTCEM
@@ -817,7 +811,6 @@ HORZ
hostable
hostlib
HPA
hpcon
HPCON
hpj
HPR
@@ -1825,7 +1818,6 @@ SYSCOMMAND
SYSDEADCHAR
SYSKEYDOWN
SYSKEYUP
SYSLIB
SYSLINK
SYSMENU
sysparams
@@ -2191,7 +2183,6 @@ wnd
WNDALLOC
WNDCLASS
WNDCLASSEX
WNDCLASSEXW
WNDCLASSW
Wndproc
WNegative
@@ -2297,7 +2288,6 @@ YOffset
YSubstantial
YVIRTUALSCREEN
YWalk
zabcd
Zabcdefghijklmnopqrstuvwxyz
ZCmd
ZCtrl

View File

@@ -7,7 +7,6 @@ on:
- labeled
- unlabeled
permissions: {}
jobs:
add-to-project:
name: Add issue to project

26
.wt.json Normal file
View File

@@ -0,0 +1,26 @@
{
"actions":
[
{
"command": { "action": "sendInput", "input": "bx\r" },
"name": "Build project",
"description": "Build the project in the CWD"
},
{
"command": { "action": "sendInput", "input": "bz\r" },
"name": "Build solution, incremental",
"description": "Just build changes to the solution"
},
{
"command": { "action": "sendInput", "input": "bcz\r" },
"name": "Clean & build solution",
"description": "Start over. Go get your coffee. "
},
{
"command": { "action": "sendInput", "input": "nuget push -apikey az -source TerminalDependencies %userprofile%\\Downloads" },
"name": "Upload package to nuget feed",
"description": "Go download a .nupkg, put it in ~/Downloads, and use this to push to our private feed."
},
]
}

View File

@@ -101,7 +101,7 @@ If you don't have any additional info/context to add but would like to indicate
If you're able & willing to help fix issues and/or implement features, we'd love your contribution!
The best place to start is the list of ["good first issue"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22++label%3A%22good+first+issue%22+)s. These are bugs or tasks that we on the team believe would be easier to implement for someone without any prior experience in the codebase. Once you're feeling more comfortable in the codebase, feel free to just use the ["Help Wanted"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+) label, or just find an issue you're interested in and hop in!
The best place to start is the list of ["Easy Starter"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+label%3A%22Easy+Starter%22+) issues. These are bugs or tasks that we on the team believe would be easier to implement for someone without any prior experience in the codebase. Once you're feeling more comfortable in the codebase, feel free to just use the ["Help Wanted"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+) label, or just find an issue your interested in and hop in!
Generally, we categorize issues in the following way, which is largely derived from our old internal work tracking system:
* ["Bugs"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Bug%22+) are parts of the Terminal & Console that are not quite working the right way. There's code to already support some scenario, but it's not quite working right. Fixing these is generally a matter of debugging the broken functionality and fixing the wrong code.

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +0,0 @@
{
"Version": "1.0.0",
"UseMinimatch": false,
"SignBatches": [
{
"MatchedPath": [
// Namespaced DLLs
"Microsoft.Terminal.*.dll",
"Microsoft.Terminal.*.winmd",
// ConPTY and DefTerm
"OpenConsole.exe",
"OpenConsoleProxy.dll",
// VCRT Forwarders
"*_app.dll",
// Legacy DLLs with old names
"TerminalApp.dll",
"TerminalApp.winmd",
"TerminalConnection.dll",
"TerminalThemeHelpers.dll",
"WindowsTerminalShellExt.dll",
// The rest
"TerminalAzBridge.exe",
"wt.exe",
"WindowsTerminal.exe",
"elevate-shim.exe"
],
"SigningInfo": {
"Operations": [
{
"KeyCode": "CP-230012",
"OperationSetCode": "SigntoolSign",
"Parameters": [
{
"parameterName": "OpusName",
"parameterValue": "Microsoft"
},
{
"parameterName": "OpusInfo",
"parameterValue": "http://www.microsoft.com"
},
{
"parameterName": "FileDigest",
"parameterValue": "/fd \"SHA256\""
},
{
"parameterName": "PageHash",
"parameterValue": "/NPH"
},
{
"parameterName": "TimeStamp",
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
}
],
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "CP-230012",
"OperationSetCode": "SigntoolVerify",
"Parameters": [],
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
}
},
{
// THIRD PARTY SOFTWARE
"MatchedPath": [
"cpprest*.dll"
],
"SigningInfo": {
"Operations": [
{
"KeyCode": "CP-231522",
"OperationSetCode": "SigntoolSign",
"Parameters": [
{
"parameterName": "OpusName",
"parameterValue": "Microsoft"
},
{
"parameterName": "OpusInfo",
"parameterValue": "http://www.microsoft.com"
},
{
"parameterName": "FileDigest",
"parameterValue": "/fd \"SHA256\""
},
{
"parameterName": "PageHash",
"parameterValue": "/NPH"
},
{
"parameterName": "TimeStamp",
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
}
],
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "CP-231522",
"OperationSetCode": "SigntoolVerify",
"Parameters": [],
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
}
}
]
}

View File

@@ -14,16 +14,16 @@
<!-- Mandatory. Name of the NuGet package which will contain PGO databases for consumption by build system. -->
<PGOPackageName>Microsoft.Internal.Windows.Terminal.PGODatabase</PGOPackageName>
<!-- Mandatory. Major version number of the PGO database which should match the version of the product. This can be hard-coded or obtained from other sources in build system. -->
<!-- Mandatory. Major version number of the PGO database which should match the version of the product. This can be hardcoded or obtained from other sources in build system. -->
<PGOPackageVersionMajor>$(VersionMajor)</PGOPackageVersionMajor>
<!-- Mandatory. Minor version number of the PGO database which should match the version of the product. This can be hard-coded or obtained from other sources in build system. -->
<!-- Mandatory. Minor version number of the PGO database which should match the version of the product. This can be hardcoded or obtained from other sources in build system. -->
<PGOPackageVersionMinor>$(VersionMinor)</PGOPackageVersionMinor>
<!-- Mandatory, defaults to 0. Patch version number of the PGO database which should match the version of the product. This can be hard-coded or obtained from other sources in build system. -->
<!-- Mandatory, defaults to 0. Patch version number of the PGO database which should match the version of the product. This can be hardcoded or obtained from other sources in build system. -->
<PGOPackageVersionPatch>0</PGOPackageVersionPatch>
<!-- Optional, defaults to empty. Prerelease version number of the PGO database which should match the version of the product. This can be hard-coded or obtained from other sources in build system. -->
<!-- Optional, defaults to empty. Prerelease version number of the PGO database which should match the version of the product. This can be hardcoded or obtained from other sources in build system. -->
<PGOPackageVersionPrerelease></PGOPackageVersionPrerelease>
<!-- Mandatory. Path to nuget.config file for the project. Path is relative to where the props file will be. -->

View File

@@ -63,7 +63,6 @@ parameters:
- Win11
variables:
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\MakeAppx.exe'
TerminalInternalPackageVersion: "0.0.7"
# If we are building a branch called "release-*", change the NuGet suffix
# to "preview". If we don't do that, XES will set the suffix to "release1"
@@ -271,28 +270,6 @@ jobs:
displayName: 'Generate SBOM manifest'
inputs:
BuildDropPath: '$(System.ArtifactsDirectory)/appx'
- pwsh: |-
$Package = (Get-ChildItem "$(Build.ArtifactStagingDirectory)/appx" -Recurse -Filter "Cascadia*.msix" | Select -First 1)
$PackageFilename = $Package.FullName
Write-Host "##vso[task.setvariable variable=WindowsTerminalPackagePath]${PackageFilename}"
& "$(MakeAppxPath)" unpack /p $PackageFilename /d "$(Build.SourcesDirectory)\UnpackedTerminalPackage"
displayName: Unpack the new Terminal package for signing
- task: EsrpCodeSigning@1
displayName: Submit Terminal's binaries for signing
inputs:
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
FolderPath: '$(Build.SourcesDirectory)\UnpackedTerminalPackage'
signType: batchSigning
batchSignPolicyFile: '$(Build.SourcesDirectory)\build\config\ESRPSigning_Terminal.json'
- pwsh: |-
$PackageFilename = "$(WindowsTerminalPackagePath)"
Remove-Item "$(Build.SourcesDirectory)\UnpackedTerminalPackage\CodeSignSummary*"
& "$(MakeAppxPath)" pack /h SHA256 /o /p $PackageFilename /d "$(Build.SourcesDirectory)\UnpackedTerminalPackage"
displayName: Re-pack the new Terminal package after signing
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (appx)
inputs:

View File

@@ -6,10 +6,6 @@ steps:
- task: NuGetAuthenticate@0
- script: |-
echo ##vso[task.setvariable variable=NUGET_RESTORE_MSBUILD_ARGS]/p:Platform=$(BuildPlatform)
displayName: Ensure NuGet restores for $(BuildPlatform)
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
# This should be `task: NuGetCommand@2`
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2

View File

@@ -15,9 +15,9 @@
<VersionBuildRevision Condition="'$(TerminalTargetWindowsVersion)'=='Win11' and '$(VersionBuildRevision)'!=''">$([MSBuild]::Add($(VersionBuildRevision), 1))</VersionBuildRevision>
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2023</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2022</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>18</VersionMinor>
<VersionMinor>17</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -2,6 +2,7 @@
<!-- The packages.config acts as the global version for all of the NuGet packages contained within. -->
<packages>
<!-- Native packages -->
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />

View File

@@ -59,7 +59,7 @@ To modify settings specific to the current application, invoke the `Properties`
When console applications are launched, the Windows Console Host determines which settings to use by overlaying settings from the above locations.
1. Initialize settings based on hard-coded defaults
1. Initialize settings based on hardcoded defaults
2. Overlay settings specified by the user's configured defaults
3. Overlay application-specific settings from either the registry or the shortcut file, depending on how the application was launched

View File

@@ -145,7 +145,7 @@ Our only backport successes really come from corporations with massive addressab
It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk.
So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere every time we make a change. But that's just not how it is today.
So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere everytime we make a change. But that's just not how it is today.
Original Source: https://github.com/microsoft/terminal/issues/279#issuecomment-439179675

View File

@@ -82,14 +82,7 @@
"properties": {
"colorScheme": {
"description": "The name of a color scheme to use when unfocused.",
"oneOf": [
{
"$ref": "#/$defs/SchemePair"
},
{
"type": "string"
}
]
"type": "string"
},
"foreground": {
"$ref": "#/$defs/Color",
@@ -240,21 +233,6 @@
},
"type": "object"
},
"SchemePair": {
"description": "Contains both a light and dark color scheme for the Terminal to use, depending on the theme of the application.",
"properties": {
"light": {
"default": "Campbell",
"description": "Name of the scheme to use when the app is using light theme",
"type": "string"
},
"dark": {
"default": "Campbell",
"description": "Name of the scheme to use when the app is using dark theme",
"type": "string"
}
}
},
"FontConfig": {
"properties": {
"face": {
@@ -561,160 +539,6 @@
},
"type": "object"
},
"NewTabMenuEntryType": {
"enum": [
"source",
"profile",
"folder",
"separator",
"remainingProfiles",
"matchProfiles"
]
},
"NewTabMenuEntry": {
"properties": {
"type": {
"description": "The type of menu entry",
"$ref": "#/$defs/NewTabMenuEntryType"
}
},
"required": [
"type"
],
"type": "object"
},
"FolderEntryInlining": {
"enum": [
"never",
"auto"
]
},
"FolderEntry": {
"description": "A folder entry in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "folder"
},
"name": {
"type": "string",
"default": "",
"description": "The name of the folder to show in the menu"
},
"icon": {
"$ref": "#/$defs/Icon"
},
"entries": {
"type": "array",
"description": "The entries to put inside this folder",
"minItems": 1,
"items": {
"$ref": "#/$defs/NewTabMenuEntry"
}
},
"inline": {
"description": "When set to auto and the folder only has a single entry, the entry will show directly and no folder will be rendered",
"default": "never",
"$ref": "#/$defs/FolderEntryInlining"
},
"allowEmpty": {
"description": "Whether to render a folder without entries, or to hide it",
"default": "false",
"type": "boolean"
}
}
}
]
},
"SeparatorEntry": {
"description": "A separator in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "separator"
}
}
}
]
},
"ProfileEntry": {
"description": "A profile in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "profile"
},
"profile": {
"type": "string",
"default": "",
"description": "The name or GUID of the profile to show in this entry"
}
}
}
]
},
"RemainingProfilesEntry": {
"description": "The set of profiles that are not yet explicitly included in another entry, such as the profile or source entries. This entry can be used at most one time!",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "remainingProfiles"
}
}
}
]
},
"MatchProfilesEntry": {
"description": "A set of profiles all matching the given name, source, or command line, to show in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "matchProfiles"
},
"name": {
"type": "string",
"default": "",
"description": "The name of the profiles to match"
},
"source": {
"type": "string",
"default": "",
"description": "The source of the profiles to match"
},
"commandline": {
"type": "string",
"default": "",
"description": "The command line of the profiles to match"
}
}
}
]
},
"SwitchToAdjacentTabArgs": {
"oneOf": [
{
@@ -1722,145 +1546,6 @@
}
]
},
"ShowCloseButton": {
"enum": [
"always",
"hover",
"never"
],
"type": "string"
},
"ThemeColor": {
"description": "A special kind of color for use in themes. Can be an #rrggbb color, #rrggbbaa color, or a special value. 'accent' is evaluated as the user's selected Accent color in the OS, and 'terminalBackground' will be evaluated as the background color of the active terminal pane.",
"oneOf": [
{
"pattern": "^#[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{2})?)?$",
"type": "string",
"format": "color",
"default": "#000000ff"
},
{
"const": "accent",
"type": "string"
},
{
"const": "terminalBackground",
"type": "string"
},
{
"type": "null"
}
]
},
"TabTheme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the tabs",
"properties": {
"background": {
"description": "The color of a tab when it is the active tab",
"$ref": "#/$defs/ThemeColor"
},
"unfocusedBackground": {
"description": "The color of a tab when it is not the active tab",
"$ref": "#/$defs/ThemeColor"
},
"showCloseButton": {
"description": "Controls the visibility of the close button on the tab",
"$ref": "#/$defs/ShowCloseButton"
}
}
},
"TabRowTheme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the tab row",
"properties": {
"background": {
"description": "The color of the tab row when the window is the foreground window.",
"$ref": "#/$defs/ThemeColor"
},
"unfocusedBackground": {
"description": "The color of the tab row when the window is inactive",
"$ref": "#/$defs/ThemeColor"
}
}
},
"WindowTheme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the window itself",
"properties": {
"applicationTheme": {
"description": "Which UI theme the Terminal should use for controls",
"enum": [ "light", "dark", "system" ],
"type": "string"
},
"useMica": {
"description": "True if the Terminal should use a Mica backdrop for the window. This will apply underneath all controls (including the terminal panes and the titlebar)",
"type": "boolean",
"default": false
}
}
},
"Theme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the window. This controls things like the titlebar, the tabs, the application theme.",
"properties": {
"name": {
"type": "string",
"description": "The name of the theme. This will be displayed in the settings UI.",
"not": {
"enum": [ "light", "dark", "system" ]
}
},
"tab": {
"$ref": "#/$defs/TabTheme"
},
"tabRow": {
"$ref": "#/$defs/TabRowTheme"
},
"window": {
"$ref": "#/$defs/WindowTheme"
}
}
},
"ThemePair": {
"additionalProperties": false,
"description": "A pair of Theme names, to allow the Terminal to switch theme based on the OS theme",
"properties": {
"light": {
"type": "string",
"description": "The name of the theme to use when the OS is in Light theme",
"default": "light"
},
"dark": {
"type": "string",
"description": "The name of the theme to use when the OS is in Dark theme",
"default": "dark"
}
}
},
"NewTabMenu": {
"description": "Defines the order and structure of the 'new tab' menu. It can consist of e.g. profiles, folders, and separators.",
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/$defs/FolderEntry"
},
{
"$ref": "#/$defs/SeparatorEntry"
},
{
"$ref": "#/$defs/ProfileEntry"
},
{
"$ref": "#/$defs/MatchProfilesEntry"
},
{
"$ref": "#/$defs/RemainingProfilesEntry"
}
]
}
},
"Keybinding": {
"additionalProperties": false,
"properties": {
@@ -2239,32 +1924,20 @@
},
"type": "array"
},
"newTabMenu": {
"$ref": "#/$defs/NewTabMenu"
},
"language": {
"default": "",
"description": "Sets an override for the app's preferred language, expressed as a BCP-47 language tag like en-US.",
"type": "string"
},
"theme": {
"default": "dark",
"description": "Sets the theme of the application. This value should be the name of one of the themes defined in `themes`. The Terminal also includes the themes `dark`, `light`, and `system`.",
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/$defs/ThemePair"
}
]
},
"themes": {
"description": "The list of available themes",
"items": {
"$ref": "#/$defs/Theme"
},
"type": "array"
"default": "system",
"description": "Sets the theme of the application. The special value \"system\" refers to the active Windows system theme.",
"enum": [
"light",
"dark",
"system"
],
"type": "string"
},
"showTabsInTitlebar": {
"default": true,
@@ -2497,14 +2170,7 @@
"colorScheme": {
"default": "Campbell",
"description": "Name of the terminal color scheme to use. Color schemes are defined under \"schemes\".",
"oneOf": [
{
"$ref": "#/$defs/SchemePair"
},
{
"type": "string"
}
]
"type": "string"
},
"commandline": {
"description": "Executable used in the profile.",
@@ -2730,10 +2396,7 @@
},
"startingDirectory": {
"description": "The directory the shell starts in when it is loaded.",
"type": [
"string",
"null"
]
"type": "string"
},
"suppressApplicationTitle": {
"description": "When set to true, tabTitle overrides the default title of the tab and any title change messages from the application will be suppressed. When set to false, tabTitle behaves as normal.",

View File

@@ -1,7 +1,7 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-5-13
last updated: 2022-11-18
last updated: 2020-08-04
issue id: 1571
---
@@ -76,34 +76,6 @@ There are five `type`s of objects in this menu:
- The `"entries"` property specifies a list of menu entries that will appear
nested under this entry. This can contain other `"type":"folder"` groups as
well!
- The `"inline"` property accepts two values
- `auto`: When the folder only has one entry in it, don't actually create a
nested layer to then menu. Just place the single entry in the layer that
folder would occupy. (Useful for dynamic profile sources with only a
single entry).
- `never`: (**default**) Always create a nested entry, even for a single
sub-item.
- The `allowEmpty` property will force this entry to show up in the menu, even
if it doesn't have any profiles in it. This defaults to `false`, meaning
that folders without any entries in them will just be ignored when
generating the menu. This will be more useful with the `matchProfile` entry,
below.
When this is true, and the folder is empty, we should add a
placeholder `<empty>` entry to the menu, to indicate that no profiles were
in that folder.
- _This setting is probably pretty niche, and not a requirement_. More of a
theoretical suggestion than anything.
- In the case of no entries for this folder, we should make sure to also
reflect the `inline` property:
- `allowEmpty:true`, `inline:auto`: just ignore the entry at all. Don't
add a placeholder to the parent list.
- `allowEmpty:true`, `inline:never`: Add a nested entry, with an
`<empty>` placeholder.
- `allowEmpty:false`, `inline:auto`: just ignore the entry at all. Don't
add a placeholder to the parent list.
- `allowEmpty:false`, `inline:never`: just ignore the entry at all. Don't
add a placeholder to the parent list.
* `"type":"action"`: This represents a menu entry that should execute a specific
`ShortcutAction`.
- the `id` property will specify the global action ID (see [#6899], [#7175])
@@ -125,16 +97,6 @@ There are five `type`s of objects in this menu:
enabling all other profiles to also be accessible.
- The "name" of these entries will simply be the name of the profile
- The "icon" of these entries will simply be the profile's icon
- This won't include any profiles that have been included via `matchProfile`
entries (below)
* `"type": "matchProfile"`: Expands to all the profiles that match a given
string. This lets the user easily specify a whole collection of profiles for a
folder, without needing to add them all manually.
- `"name"`, `"commandline"` or `"source"`: These three properties are used to
filter the list of profiles, based on the matching property in the profile
itself. The value is a string to compare with the corresponding property in
the profile. A full string comparison is done - not a regex or partial
string match.
The "default" new tab menu could be imagined as the following blob of json:
@@ -146,42 +108,6 @@ The "default" new tab menu could be imagined as the following blob of json:
}
```
Alternatively, we could consider something like the following. This would place
CMD, PowerShell, and all PowerShell cores in the root at the top, followed by
nested entries for each subsequent dynamic profile generator.
```jsonc
{
"newTabMenu": [
{ "type":"profile", "profile": "cmd" },
{ "type":"profile", "profile": "Windows PowerShell" },
{ "type": "matchProfile", "source": "Microsoft.Terminal.PowerShellCore" }
{
"type": "folder",
"name": "WSL",
"entries": [ { "type": "matchProfile", "source": "Microsoft.Terminal.Wsl" } ]
},
{
"type": "folder",
"name": "Visual Studio",
"entries": [ { "type": "matchProfile", "source": "Microsoft.Terminal.VisualStudio" } ]
},
// ... etc for other profile generators
{ "type": "remainingProfiles" }
]
}
```
I might only recommend that for `userDefaults.json`, which is the json files
used as a template for a user's new settings file. This would prevent us from
moving the user's cheese too much, if they're already using the Terminal and
happy with their list as is. Especially consider someone who's default profile
is a WSL distro, which would now need two clicks to get to.
> _note_: We will also want to support the same `{ "key": "SomeResourceString"}`
> syntax used by the Command Palette commands
> for specifying localizable names, if we chose to pursue this route.
### Other considerations
Also considered during the investigation for this feature was re-using the list
@@ -228,42 +154,6 @@ The design chosen in this spec more cleanly separates the responsibilities of
the list of profiles and the contents of the new tab menu. This way, each object
can be defined independent of the structure of the other.
Regarding implementation of `matchProfile` entries: In order to build the menu,
we'll evaluate the entries in the following order:
* all explicit `profile` entries
* then all `matchProfile` entries, using profiles not already specified
* then expand out `remainingProfiles` with anything not found above.
As an example:
```jsonc
{
"newTabMenu": [
{ "type": "matchProfile", "source": "Microsoft.Terminal.Wsl" }
{
"type": "folder",
"name": "WSLs",
"entries": [ { "type": "matchProfile", "source": "Microsoft.Terminal.Wsl" } ]
},
{ "type": "remainingProfiles" }
]
}
```
For profiles { "Profile A", "Profile B (WSL)", "Profile C (WSL)" }, This would
expand to:
```
New Tab Button ▽
├─ Profile A
├─ Profile B (WSL)
├─ Profile C (WSL)
└─ WSLs
└─ Profile B (WSL)
└─ Profile C (WSL)
```
## UI/UX Design
See the above _figure 1_.
@@ -399,39 +289,7 @@ And assuming the user has bound:
- Close Tab: `{ "action": "closeTab", "index": "${selectedTab.index}" }`
- Close Other Tabs: `{ "action": "closeTabs", "otherThan": "${selectedTab.index}" }`
- Close Tabs to the Right: `{ "action": "closeTabs", "after": "${selectedTab.index}" }`
* We may want to consider regex, tag-based, or some other type of matching for
`matchProfile` entries in the future. We originally considered using regex for
`matchProfile` by default, but decided instead on full string matches to leave
room for regex matching in the future. Should we chose to pursue something
like that, we should use a settings structure like:
```json
"type": "profileMatch",
"source": { "type": "regex", "value": ".*wsl.*" }
```
* We may want to expand `matchProfile` to match on other properties too. (`title`?)
* We may want to consider adding support for capture groups, e.g.
```json
{
"type": "profileMatch",
"name": { "type": "regex", "value": "^ssh: (.*)" }
}
```
for matching to all your `ssh: ` profiles, but populate the name in the entry
with that first capture group. So, ["ssh: foo", "ssh: bar"] would just expand
to a "foo" and "bar" entry.
## Updates
_February 2022_: Doc updated in response to some discussion in [#11326] and
[#7774]. In those PRs, it became clear that there needs to be a simple way of
collecting up a whole group of profiles automatically for sorting in these
menus. Although discussion centered on how hard it would be for extensions to
provide that customization themselves, the `match` statement was added as a way
to allow the user to easily filter those profiles themselves.
This was something we had originally considered as a "future consideration", but
ultimately deemed it to be out of scope for the initial spec review.
<!-- Footnotes -->
[#2046]: https://github.com/microsoft/terminal/issues/2046
@@ -440,5 +298,3 @@ ultimately deemed it to be out of scope for the initial spec review.
[#3337]: https://github.com/microsoft/terminal/issues/3337
[#6899]: https://github.com/microsoft/terminal/issues/6899
[#7175]: https://github.com/microsoft/terminal/issues/7175
[#11326]: https://github.com/microsoft/terminal/issues/11326
[#7774]: https://github.com/microsoft/terminal/issues/7774

View File

@@ -201,7 +201,7 @@ Concerns:
### Accessibility
Accessibility applications are the most likely to resort to a method of spelunking the process tree or window handles to attempt to find content to read out. Presuming they have hard-coded rules for console-type applications, these algorithms could be surprised by the substitution of another terminal environment.
Accessibility applications are the most likely to resort to a method of spelunking the process tree or window handles to attempt to find content to read out. Presuming they have hardcoded rules for console-type applications, these algorithms could be surprised by the substitution of another terminal environment.
The major players here that I am considering are NVDA, JAWS, and Narrator. As far as I am aware, all of these applications attempt to drive their interactivity through UI Automation where possible. And we have worked with all of these applications in the past in improving their support for both `conhost.exe` and the Windows Terminal product. I have relatively high confidence that we will be able to work with them again to help update these assistive products to understand the new UI delegation, if necessary.

View File

@@ -76,7 +76,7 @@ These are in accordance with ConHost's keyboard selection model.
This idea was abandoned due to several reasons:
1. Keyboard selection should be a standard way to interact with a terminal across all consumers (i.e. WPF control, etc.)
2. There isn't really another set of key bindings that makes sense for this. We already hard-coded <kbd>ESC</kbd> as a way to clear the selection. This is just an extension of that.
2. There isn't really another set of key bindings that makes sense for this. We already hardcoded <kbd>ESC</kbd> as a way to clear the selection. This is just an extension of that.
3. Adding 12 conditionally effective key bindings takes the spot of 12 potential non-conditional key bindings. It would be nice if a different key binding could be set when the selection is not active, but that makes the settings design much more complicated.
4. 12 new items in the command palette is also pretty excessive.
5. If proven wrong when this is in WT Preview, we can revisit this and make them customizable then. It's better to add the ability to customize it later than take it away.

View File

@@ -115,7 +115,7 @@ greater detail below:
### Default Settings
We'll have a static version of the "Default" file **hard-coded within the
We'll have a static version of the "Default" file **hardcoded within the
application package**. This `defaults.json` file will live within the
application's package, which will prevent users from being able to edit it.
@@ -128,19 +128,19 @@ won't actually be generated, but because it's shipped with our app, it'll be
overridden each time the app is updated. "Auto-generated" should be good enough
to indicate to users that it should not be modified.
Because the `defaults.json` file is hard-coded within our application, we can use
Because the `defaults.json` file is hardcoded within our application, we can use
its text directly, without loading the file from disk. This should help save
some startup time, as we'll only need to load the user settings from disk.
When we make changes to the default settings, or we make changes to the settings
schema, we should make sure that we update the hard-coded `defaults.json` with
schema, we should make sure that we update the hardcoded `defaults.json` with
the new values. That way, the `defaults.json` file will always have the complete
set of settings in it.
### Layering settings
When we load the settings, we'll do it in three stages. First, we'll deserialize
the default settings that we've hard-coded. We'll then generate any profiles that
the default settings that we've hardcoded. We'll then generate any profiles that
might come from dynamic profile sources. Then, we'll intelligently layer the
user's setting upon those we've already loaded. If a user wants to make changes
to some objects, like the default profiles, we'll need to make sure to load from

View File

@@ -158,7 +158,7 @@ For `settings.json`, `_globals` will only hold the values set in `settings.json`
This process becomes a bit more complex for `Profile` because it can fallback in the following order:
1. `settings.json` profile
2. `settings.json` `profiles.defaults`
3. (if a dynamic profile) the hard-coded value in the dynamic profile generator
3. (if a dynamic profile) the hardcoded value in the dynamic profile generator
4. `defaults.json` profile
`CascadiaSettings` must do the following...
@@ -280,7 +280,7 @@ TerminalApp will construct and reference a `CascadiaSettings settings` as follow
and layers the settings.json data on top of it.
- check for errors/warnings, and handle them appropriately
This will be different from the current model which has the settings.json path hard-coded, and is simplified
This will be different from the current model which has the settings.json path hardcoded, and is simplified
to a `LoadAll()` call wrapped in error handlers.
**NOTE:** This model allows us to layer even more settings files on top of the existing Terminal Settings

View File

@@ -1,410 +0,0 @@
---
author: Mike Griese @zadjii-msft
created on: 2021-03-03
last updated: 2022-11-04
issue id: #2634
---
# Broadcast Input
## Abstract
"Broadcast Input" is a feature present on other terminals which allows the user
to send the same input to multiple tabs or panes at the same time. This can make
it simpler for the user to run the same command in multiple directories or
servers at the same time.
With a viable prototype in [#9222], it's important that we have a well-defined
plan for how we want this feature to be exposed before merging that PR. This
spec is intended to be a lighter-than-usual spec to build consensus on the
design of how the actions should be expressed.
## Background
### Inspiration
This spec is heavily inspired by the [iTerm2 implementation]. @carlos-zamora did
a great job of breaking down how iTerm2 works in [this comment].
SecureCRT also implements a similar feature using a "chat window" that can send
the input in the chat window to all tabs. This seemed like a less ergonomic
solution, so it was not seriously considered.
Additionally, Terminator (on \*nix) allows for a similar feature through the use
of "groups". From [@zljubisic]:
> In Linux terminator you can define groups, and than put each pane in one of
> defined groups. Afterwards, you can choose broadcasting to all panes, only
> certain group or no broadcast at all.
This also seemed like a less powerful version of broadcast input than the
iterm2-like version, so it was also not further investigated.
### User Stories
iTerm2 supports the following actions:
* **Story A:** _Send input to current session only_: The default setting.
* **Story B:** _Broadcast to all panes in all tabs_: Anything you type on the
keyboard goes to all sessions in this window.
* **Story C:** _Broadcast to all panes in current tab_: Anything you type on the
keyboard goes to all sessions in this tab.
* **Story D:** _Toggle broadcast input to current session_: Toggles whether this
session receives broadcasted keystrokes within this window.
## Solution Design
### Proposal 1: iTerm2-like Modal Input Broadcast
iTerm2 implements broadcast input as a type of "modal" system. The user is in
one of the following modes:
* Broadcast to all panes in all tabs
* Broadcast to all panes in the current tab
* Broadcast to some set of panes within the current tab
* Don't broadcast input at all (the default behavior)
These modes are vaguely per-tab state. There's a global "broadcast to all tabs &
panes" property. Then, each tab also has a pair of values:
* Should input be sent to all panes in this tab?
* If not, which panes should input be sent to?
It's not possible to send input to one pane in tab A, then another pane in tab
B, without enabling the global "broadcast to everyone" mode.
This seems to break down into the following actions:
```json
{ "action": "toggleBroadcastInput", "scope": "window" },
{ "action": "toggleBroadcastInput", "scope": "tab" },
{ "action": "toggleBroadcastInput", "scope": "pane" },
{ "action": "disableBroadcastInput" },
```
Which would be accompanied by the following internal properties:
* A window (`TerminalPage`-level) property for `broadcastToAllPanesAndTabs`
* A per-tab property for `broadcastToAllPanes`
* A per-tab set of panes to broadcast to
The scopes would work as follows:
* `"scope": "window"`: Toggle the window's "broadcast to all tabs and panes"
setting.
* `"scope": "tab"`: Toggle the tab's "broadcast to all panes in this tab"
setting.
- This does not modify the set of panes that the user is broadcasting to in
the tab, merely toggles the tab's setting. If the user has a set of panes
they're broadcasting to in this tab, then toggles this setting on and off,
we'll return to broadcasting to that set.
* `"scope": "pane"`: Add this pane to the set of panes being broadcasted to in
this tab.
- **TODO!: FOR DISCUSSION**: Should this disable the tab's
"broadcastToAllPanes" setting? Or should it leave that alone?
* `"disableBroadcastInput"`: Set the global setting to false, the tab's setting
to false, and clear the set of panes being broadcasted to for this tab.
- **TODO!** This could also just be `"action": "toggleBroadcastInput",
"scope": "none"`
#### Pros
* This is exactly how iTerm2 does it, so there's prior art.
* If you're not globally broadcasting, then you're only ever broadcasting to
some (sub)set of the panes in the current tab. So global broadcast mode is
the only time a user would need to worry about input being to be sent to
an inactive tab.
* You can have a set of panes to broadcast to in the first tab, then a
_separate_ set to broadcast to in a second tab. Broadcasting in one tab
does not affect the other.
#### Cons
* I frankly think the `tab`/`pane` interaction can be a little weird. Like for
this scenario:
- enable broadcast input for tab 1
- switch to tab 2
- enable broadcast input for a pane in tab 2
There's valid confusion to be had between the following two behaviors:
1. input goes to all of tab 1 _and_ the pane in tab 2
2. input only goes to the pane in tab 2
* You can't broadcast to a subset of panes in inactive tabs, in addition to
the active tab. All panes you want to broadcast to must be in the active
tab.
* Does creating a new split in a pane that's being broadcast to add that pane to
the broadcast set?
#### What would this mean for PR #9222?
The prototype PR [#9222] basically just implemented `{ "action":
"toggleBroadcastInput", "scope": "tab" }`. We could make `tab` the default
`scope` if no other one is specified, and then the PR would need basically no
modifications. Future PRs could add args to the `toggleBroadcastInput` action,
without breaking users who bind a key to that action now.
### Proposal 2: Broadcast Set
This was the design I had originally came up with before investigating iTerm2
much closer. This design involves a "broadcast set" of panes. All the panes in
the broadcast set would also get the `KeySent` and `CharSent` events, in
addition to the active pane. (The active pane may be a part of the broadcast
set). If a pane is read-only in the broadcast set, then it won't handle those
broadcasted events (obviously).
As far as actions, we're looking at something like:
* **A** Only send input to the active pane
* Remove all the panes from the broadcast set
* **B** send input to all panes in all tabs
* If all the panes are in the broadcast set, remove them all. Otherwise, add
all panes in all tabs to the broadcast set.
* **C** send input to all panes in the current tab
* If all the panes in the current tab are in the broadcast set, remove them
from the broadcast set. Otherwise, add all the panes from this tab to the
broadcast set.
* **D** toggle sending input to the current pane
* If this pane is in the broadcast set, remove it. Otherwise add it.
This seems to break down into the following actions:
```json
{ "action": "disableBroadcastInput" },
{ "action": "toggleBroadcastInput", "scope": "window" },
{ "action": "toggleBroadcastInput", "scope": "tab" },
{ "action": "toggleBroadcastInput", "scope": "pane" },
```
Which would be accompanied by the following internal properties:
* A window (`TerminalPage`-level) set of panes to broadcast to.
#### Pros:
* Mentally, you're either adding panes to the set of panes to broadcast to, or
removing them.
* You can broadcast to panes in multiple tabs, without broadcasting to _all_
panes in all tabs.
#### Cons:
* is _slightly_ different from iTerm2.
* Does creating a new split in a pane that's being broadcast to add that pane to
the broadcast set?
* You can't have a set of panes to broadcast to in the one tab, and a different
set in another tab. As an example:
1. in tab 1, you add panes A and B to the broadcast set. Typing in either one
goes to both A and B.
2. in tab 1, switch to pane C. Now input goes to A, B and C.
3. in tab 1, switch to pane D. Now input goes to A, B and D.
4. switch to tab 2, pane E. Now input goes to A, B and E.
You can't have like, a set with A & B (in 1), then E & F (in 2). So if someone
wants to type to both panes in 1, then both panes in 2, then both panes in 1,
they need to keep toggling which panes are in the broadcast set.
#### What would this mean for PR #9222?
Similar to Proposal 1, we'd use `tab` as the default value for `scope`. In the
future, when we add support for the other scopes, we'd change how the
broadcasting works, to use a set of panes to broadcast to, instead of just the
tab-level property.
### Proposal 3: It's iTerm2, but slightly different
While typing this up, I thought maybe it might make more sense if we took the
iTerm2 version, and changed it slightly:
* `"scope": "tab"`: If all the panes are in the broadcast set for this tab, then
remove them all. Otherwise, add all the panes in this tab to this tab's
broadcast set.
* `"scope": "pane"`: If this pane is in the broadcast set for a tab, then remove
it. Otherwise, add it.
With this, we get rid of the tab-level setting for "broadcast to all the panes
in this tab", and rely only on the broadcast set for that tab.
#### Pros:
* All the pros from proposal A
* Does away with the seemingly weird toggling between "all the panes in a tab"
and "some of the panes in a tab" that's possible with proposal A
#### Cons:
* You can't broadcast to a subset of panes in inactive tabs, in addition to
the active tab. All panes you want to broadcast to must be in the active
tab.
* is _slightly_ different from iTerm2. Just _slightly_.
* Does creating a new split in a pane that's being broadcast to add that pane to
the broadcast set?
#### What would this mean for PR #9222?
Same as with proposal A, we wouldn't change anything in the current PR. A future
PR that would add the other scope's to that action would need to change how the
broadcasting within a tab works, to use a set of panes to broadcast to, instead
of just the tab-level property.
## Conclusion
I'm proposing these settings for broader discussion. I'm not really sure which I
like most at this point. 1 & 3 have the advantage of being most similar to the
prior art, but 2 is more easily extendable to "groups" (see [Future
Considerations](#Future-Considerations)).
**TODO!**: Make a decision.
_**Fortunately**_: All these proposals actually use the same set of actions. So
it doesn't _really_ matter which we pick right now. We can unblock [#9222] as
the implementation of the `"tab"` scope, and address other scopes in the future.
We should still decide long-term which of these we'd like, but the actions seem
universal.
## UI/UX Design
This is supposed to be a quick & dirty spec, so I'm LARGELY skipping this.
As far as indicators go, we'll throw something like:
![NetworkTower Segoe UI Icon](broadcast-segoe-icon.png)
in the tab when a pane is being broadcasted to. If all tabs are being
broadcasted to, then they'll all have that icon. If a tab is inactive, and any
pane in that tab is being broadcast to, then show the icon in the tab.
It probably makes the most sense to have pane titlebars ([#4998]) also display
that icon.
In the original PR, it was suggested to use some variant of the [accent color]
to on the borders of panes that are currently receiving broadcasted input. We're
already using the accent color on the borders of the active pane.
`SystemAccentColorLight*`/`SystemAccentColorDark*` would provide a way of using
a similar hue with different lightness/saturation. This would be a decent visual
indicator that they're _not_ the active pane, but they are going to receive
input. Something a bit like:
![A sample of using the border to indicate the broadcasted-to panes](broadcast-input-borders.gif)
This should obviously be able to be overridden in the user's theme, similar to
the pane border colors.
iTerm2 also supports displaying "stripes" in the background of all the panes
that are being broadcast too. That's certainly another way of indicating this
feature to the user. I'm not sure how we'd layer it with the background image
though. **I recommend we ignore this for now, and leave this as a follow-up**.
### Tab context menu items
For reference, refer to the following from iTerm2:
![image](https://user-images.githubusercontent.com/2578976/64075757-fa971980-ccee-11e9-9e44-47aaf3bca76c.png)
We don't have a menu bar like on MacOS, but we do have a tab context menu. We
could add these items as a nested entry under each tab. If we wanted to do this,
we should also make sure to dynamically change the icon of the MenuItem to
reflect the current broadcast state.
## Potential Issues
<table>
<tr>
<td><strong>Compatibility</strong></td>
<td>
[comment]: # Will the proposed change break existing code/behaviors? If so, how, and is the breaking change "worth it"?
</td>
</tr>
</table>
[comment]: # If there are any other potential issues, make sure to include them here.
## Implementation plan
* [ ] Resurrect [#9222], and use that to implement `"scope": "tab"`. This is
implemented the same, regardless of which proposal we chose.
* [ ] Add a tab context menu entry for toggling broadcast input, with a dynamic
icon based on the current state.
* [ ] Implement `"scope": "window"`. Again, this is implemented the same regardless
of which proposal we pursue.
* [ ] Decide between the two proposals here.
* [ ] Implement `"scope": "pane"`.
Doing the first element here is probably the most important one for most users,
and can be done regardless of the proposal chosen here. As such, we could even
suggest the default value of `scope` be `tab`. If we did that, then we wouldn't
need to do any args at all in the initial version.
## Future Considerations
Let's look to iTerm2, who's supported this feature for years, for some
inspiration of future things we should be worried about. If their users have
asked for these features, then it's inevitable that our users will too 😉
* [iterm2#6709] - Broadcast Input to multiple windows
- This is pretty straightforward. Would require coordination with the Monarch
though, and I'm worried about the perf hit of tossing every keystroke across
the process boundary.
- I suppose this would be `{ "action": "toggleBroadcastInput", "scope":
"global" }`
* [iterm2#6451], [iterm2#5563] - "Broadcast commands"
- iTerm2 has an action that lets the user manually clear the terminal-side
buffer. (This is tracked on the Windows Terminal as [#1882]). It might make
sense for there to be a mode where some _actions_ are also broadcast to
panes, not just key strokes. But which actions would those be? Moving the
selection anchors? Copy doesn't really make sense. Paste _does_ though.
Maybe the open find dialog / next&prev search match actions?
- This probably would require it's own spec.
* [iterm2#6007] - Different stripe color for different broadcast modes
- Have one color to indicate when broadcasting in `global` scope, another in
`tab` scope, a third in `pane` scope.
- This might mesh well with theming ([#3327]), for properties like
`pane.broadcastBorderColor.globalScope`,
`pane.broadcastBorderColor.paneScope`. Don't love those names, but you get
the idea.
* **[iterm2#5639]: Broadcast groups**, [iterm2#3372] - Broadcast Input to
multiple but not all tabs
- This is probably the most interesting request. I think this one identifies a
major shortcoming of the above proposals. With proposal 2, there's only ever
one top-level broadcast group. With proposals 1 & 3, there's per-tab
broadcast groups. In neither proposal can you have multiple concurrent
side-by-side broadcast groups.
- Groups should probably work across tabs. This would suggest that Proposal 2
is closer to how groups would work. Instead of there being one top-level
set, there would be multiple. **I'm not sure how proposals 1&3 would
seamlessly transition into also supporting groups**.
- The major trick here is: how do we differentiate these different groups to
the user? If we used the broadcast icon with a number, maybe in the corner
of the tab? Like [📡: 1]? Can a pane be in multiple broadcast sets at the
same time?
- The natural arg idea would be `{ "action": "toggleBroadcastInput", "scope":
"tab", "group": 1 }` to say "add all panes in the tab to broadcast group 1",
or "remove all panes in the tab from broadcast group 1". If panes are in
another group, they'd be moved to the specified group. If all panes are in
that group, then remove them all.
- The UI for this would certainly get complex fast.
- This also matches the Terminator-style broadcasting to groups.
* Re: stripes in the background of the tab. We could expose a pane's current
broadcast state to the pixel shader, and a user could use a custom pixel
shader to add stripes behind the text in the shader code. That's one possible
solution.
## Resources
[comment]: # Be sure to add links to references, resources, footnotes, etc.
### Footnotes
<a name="footnote-1"><a>[1]:
[#1882]: https://github.com/microsoft/terminal/issues/1882
[#2634]: https://github.com/microsoft/terminal/issues/2634
[#4998]: https://github.com/microsoft/terminal/issues/4998
[#3327]: https://github.com/microsoft/terminal/issues/3327
[#9222]: https://github.com/microsoft/terminal/pull/9222
[this comment]: https://github.com/microsoft/terminal/issues/2634#issuecomment-789116413
[iTerm2 implementation]: https://iterm2.com/documentation-one-page.html#documentation-menu-items.html
[@zljubisic]: https://github.com/microsoft/terminal/pull/9222#issuecomment-789143189
[accent color]: https://docs.microsoft.com/en-us/windows/uwp/design/style/color#accent-color-palette
[iterm2#6709]: https://gitlab.com/gnachman/iterm2/-/issues/6709
[iterm2#6451]: https://gitlab.com/gnachman/iterm2/-/issues/6451
[iterm2#6007]: https://gitlab.com/gnachman/iterm2/-/issues/6007
[iterm2#5639]: https://gitlab.com/gnachman/iterm2/-/issues/5639
[iterm2#5563]: https://gitlab.com/gnachman/iterm2/-/issues/5563
[iterm2#3372]: https://gitlab.com/gnachman/iterm2/-/issues/3372

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,380 +0,0 @@
---
author: Mike Griese @zadjii-msft
created on: 2021-08-31
last updated: 2021-08-31
issue id: #642
---
# Buffer Exporting and Logging
## Abstract
A common user need is the ability to export the history of a terminal session to
a file, for later inspection or validation. This is something that could be
triggered manually. Many terminal emulators provide the ability to automatically
log the output of a session to a file, so the history is always captured. This
spec will address improvements to the Windows Terminal to enable these kinds of
exporting and logging scenarios.
## Background
### Inspiration
Below are screenshots from the settings pages of three different terminal
emulators with similar features - PuTTY, SecureCRT, and ConEmu:
![PuTTY settings](PuTTY-logging-settings.png)
_figure 1: PuTTY settings_
![SecureCRT settings](SecureCRT-logging-settings.png)
_figure 2: SecureCRT settings_
![ConEmu settings](ConEmu-logging-settings.png)
_figure 3: ConEmu settings_
These applications all offer some settings in common. Primarily, the important
feature is the ability to specify a path to a log file which contains some
special string formatting. This allows the user to log to different files based
on the time & date of the session, or based on the session name.
### User Stories
* **Story A**: The user is able to use a context menu entry on the tab to export
the contents of the buffer to a file, which they are prompted for.
- This is explicitly what was requested in [#642]
* **Story B**: The user can bind an action to export the contents of the buffer
to a file, which they are prompted for.
- Very similar to **A**, but via the command palette or a keybinding.
* **Story C**: The user can export to an explicit file via an action
- similar to **B**, but allowing for declaring the path to a file rather than
prompting at runtime.
* **Story D**: The user can choose to append to a file when exporting, rather
than overwriting.
* **Story E**: The user can specify a format string in the path to the file to
export to, which the Terminal will automatically replace with variables like
the time, date, and profile name.
* **Story F**: When opening a specific profile, the user can automatically log
to a file
* **Story G**: The user can execute an action to start or stop logging to a
given file.
## Solution Design
I'm proposing the following actions and profile settings
* New Action: `exportBuffer()`.
- Export the contents of the buffer to a file.
- `path` (string, defaults to `""`): When empty, prompt the user for a name of
a file to export to, using a file picker. This path accepts special
formatting strings that will be substituted with certain variables
(discussed [below](#path-formatting)).
- `append` (boolean, defaults to `false`): When `false`, the file's contents
will be overwritten. When `true`, the buffer contents will be appended to
the end of the file.
* New Profile Settings object: `logSettings`
- This is an object that describes a set of behavior for logging a profile.
- `path`: Same as the `path` in the `ExportBufferArgs` above
- `append`: Same as the `append` in the `ExportBufferArgs` above
- `captureAllOutput`: (boolean, defaults to `false`) When true, don't log only
printable characters, also log non-printable escape characters written to
the Terminal.
- `captureInput`: (boolean, defaults to `false`) Additionally log input to the
Terminal to the file. Input will be formatted as the traditional VT
sequences, rather than the full `win32-input` encoding.
- `newFileEveryDay`: (boolean, defaults to `false`) This requires the `day` to
be an element of the path format string. When logging with this setting,
opens a new file at midnight and starts writing that one.
<!-- TODO! - `flushFrequently`: (boolean, defaults to `true`) -->
* New Profile setting: `logAutomatically` (boolean, default `false`). When true,
terminals with this profile will begin logging automatically.
* New Action: `toggleLogging()`.
- Start or stop logging to the configured file. If the terminal is already
logging with different settings than in this action, then stop logging
regardless (don't just start logging to the new file)
- This action accepts all the same args the profile's `logSettings` object.
- If _any_ args are provided, use those args. If _none_ are provided, then use
the logging settings present in the profile (if there are any).
- If there's not path provided (either in the args to the action or in the
profile), prompt the user to pick a file to log to.
### Examples
```json
{
"actions": [
{ "keys": "f1", "command": "exportBuffer" },
{ "keys": "f2", "command": { "action": "exportBuffer", "path": "c:\\logs\\${year}-${month}-${date}\\{profile}.txt" } },
{ "keys": "f3", "command": "toggleLogging" },
{ "keys": "f4", "command": { "action": "toggleLogging", "path": "c:\\logs\\${profile}.log", "append": true } },
],
"profiles": [
{
"name": "foo",
"logging": {
"path": "c:\\foo.txt",
"append": true
},
"automaticallyLog": false
},
{
"name": "bar",
"logging": {
"path": "c:\\logs\\${date}\\bar.txt",
"append": false
},
"automaticallyLog": true
}
]
}
```
Revisiting our original stories:
* **Story A**: This is already implemented in [#11062]
* **Story B**: This is the action bound to <kbd>f1</kbd>.
* **Story C**: This is the action bound to <kbd>f2</kbd>.
* **Story D**: This is the `append` property in the actions, profile settings.
* **Story E**: An example of this is in the action bound to <kbd>f2</kbd>,
<kbd>f4</kbd>, and in the profile "bar"'s logging settings.
* **Story F**: The profile "bar" is configured to automatically log when opened.
* **Story G**: This is the action bound to <kbd>f4</kbd>.
In addition,
* When opening the profile "foo", it will not automatically log to a file.
- Pressing <kbd>f3</kbd> will begin logging to `c:\foo.txt`
- Pressing <kbd>f4</kbd> will begin logging to `c:\logs\foo.log`
### Path formatting
[TODO!]: # TODO!
For discussion: What syntax do we want?
* PuTTY uses `&Y`, `&M`, `&D`, `&T`, `&H`, `&P` for year, month, day, time, host
and port respectively.
* SecureCRT uses:
- `%H` hostname
- `%S` session name
- `%Y` four-digit year
- `%M` two-digit month
- `%D` two-digit day of the month
- `%h` two-digit hour
- `%m` two-digit minute
- `%s` two-digit seconds
- `%t` three-digit milliseconds
- `%%` percent (%)
- `%envvar%` environment variable (for instance `%USERNAME%`)
We have some precedent for formatting with `${braces}`, a la the iterable
command in the Command Palette (e.g `${profile.name}`). Additionally, [#9287]
implements support for environment variables in the Terminal with the
`${env:VARIABLE}` syntax.
What variables do we want exposed, and how do we want users to be able to format
them?
This doc was initially authored assuming we'd go with a `${braces}` syntax, like:
- `${profile}` profile name
- `${year}` four-digit year
- `${month}` two-digit month
- `${day}` two-digit day of the month
- `${hour}` two-digit hour
- `${minute}` two-digit minute
- `${second}` two-digit second
- `${ms}` three-digit milliseconds
- `${env:variable}` environment variable (for instance `${env:USERPROFILE}`)
(inspired by [#9287])
### Exporting vs Logging
As far as specific implementation details goes, exporting is the easier work to
do. [#11062] already wires up the `TerminalApp` to retrieve the buffer contents
from the `TermControl`, so writing them at request is easy.
Logging is harder. We don't want the `TermControl` telling the `TerminalApp`
layer about every piece of output logged. Especially in the post-[#5000] world
where that's a cross-process hop. Instead, we'll want the `ControlCore` /
`ControlInteractivity` to do _logging_ themselves.
### Logging Mechanics
#### When do we log?
[TODO!]: # TODO!
When do we decide to actually log? Take for example typing in a `pwsh` or
`bash` prompt. Imagine the user types
<kbd>w</kbd><kbd>h</kbd><kbd>a</kbd><kbd>t</kbd>, then hits
<kbd>Bksp</kbd><kbd>Bksp</kbd>, such that the prompt is just `wh`. What should
the log contain? `what^h ^h^h ^h`<sup>[[1]](#footnote-1)</sup>? `wh`?
My worry with logging the backspaces is that conpty is sometimes a bit noisier
than it needs to be with using `^H` as a cursor positioning sequence. Should we
only log lines when the cursor newlines or otherwise moves from the line it is
currently on?
I'll need to look at what PuTTY emits for the "Printable output" option.
#### What happens when we _start_ logging?
If the user has a terminal that did not start with logging enabled, but then
started logging with `toggleLogging`, what should we log? All future output? Or
should we log the current buffer contents as well?
I'm inclined to lean towards simply "all future output", and ignore any current
buffer content. If the user rally wants to log the current buffer contents _and_
start logging, they can use a `multipleActions` action ([#11045]) to
`exportBuffer` to a file, then `toggleLogging` to that same file with
`"append":true`.
## Potential Issues
<table>
<tr>
<td><strong>Compatibility</strong></td>
<td>
Since this functionality is entirely new, nothing here should negatively affect
existing functionality.
</td>
</tr>
<tr>
<td><strong>Performance, Power, and Efficiency</strong></td>
<td>
When logging, it's expected there will be a measurable performance hit. We can
try to mitigate this by only writing to the file on a background thread,
separate from the connection or rendering thread. Since auto-logging will only
take place in the content process, we're not worried about the file writing
occurring on the UI thread.
</td>
</tr>
</table>
Also frequently requested is the ability to log timestamps of when commands are
executed. I don't think that this is a valuable feature for the Terminal to
implement ourselves. Windows Terminal is fundamentally just a _terminal
emulator_, it doesn't really know what's going on behind the scenes with
whatever client application (`cmd`, `powershell`, `bash`, `vim`) that is
connected to it. WT doesn't know when the user is typing in commands to the
shell, or if the user is just typing in text in `emacs` or something. There's no
way for the terminal to know that. It's _typically_ the client application's
responsibility to save it's own command history. `bash` and `powershell` both do
a pretty good job of saving this to another file to restore across sessions,
while `cmd.exe` doesn't.
Windows is a messy world and this model gets a little tricky here. `cmd.exe`
isn't actually managing it's own command history _at all_. `conhost` is doing
that work on behalf of the client applications. Some long time ago someone
thought it would be a good idea to have the `readline` functionality baked
directly into the console host. Whether that was a good idea or not remains to
be seen - it's certainly made things like `python.exe`'s REPL easier to
implement, since they don't need to maintain their own history buffer, but it
makes it hard to de-tangle behavior like this from the console itself.
I'm not sure how it would be possible to add a keybinding to the Windows
Terminal that would be able to save the console's _command_ history. Especially
considering the Terminal might _not_ be connected to a console host session at
all. If the Windows Terminal were directly running a `wsl` instance (something
that's not possible today, but something we've considered adding in the future),
then there wouldn't be a `conhost` in the process tree at all, and now
requesting the command history from the console wouldn't work _mysteriously_.
Furthermore, shells can always be configured to emit timestamps in their prompts
themselves. Since the Terminal has no knowledge of when a command is actually
entered, but the _shell_ does, it makes the most sense to configure the user's
_shell_ to emit that information. The Terminal will then dutifully log that
output along with everything else.
## Implementation Plan
Below is a rough outline of how I'd go about implementing these features. Each
lop-level checkbox could be its own PR, following from [#11062].
### Buffer exporting
* [ ] Add an `exportBuffer()` action that opens the file picker
* [ ] Add a string `path` parameter to `exportBuffer()` that allows the user to
press a key and immediately export the buffer to a whole path
- default to `""`, which indicates "open the file picker"
* [ ] add a boolean `append` (default to `false`) parameter to `exportBuffer`.
When true, export to the file given by appending, not overwriting the file
* [ ] Enable string formatting in the `path` parameter.
- What format do we want? `yyyy-mm-dd`? `%Y-%m-%D`? `&Y-&m-&D`? `${year}-${month}-${day}`?
- What are all the variables we want?
- Year, month, day, hour, minute - those are easy
- `WT_SESSION`, for a uuid for each session maybe?
- Profile name perhaps? Commandline?
* [ ] more...
### Automatic logging
* [ ] `toggleLogging()` Action for start/stop logging, with `path`, `append`
properties (like `exportBuffer()`)
- `ToggleLoggingArgs` contains a single member `LoggingSettings`, which
contains `path` and `append` properties. This will make sense below.
* [ ] add `LoggingSettings` property for "log all output" (default would just be
"log printable output")
* [ ] add `LoggingSettings` property for "log input" (Though, we'd probably want
to log it as normal VT encoded, not as `win32-input` encoded)
* [ ] Per-profile setting for `logSettings`, which can contain an entire
`LoggingSettings` (like the `ToggleLoggingArgs`). When `toggleLogging` with no
args, try to use the profile's `loggingSettings` instead.
* [ ] Per-profile setting for `automaticallyLog`, which would log by default
when the profile is opened
* [ ] `LoggingSettings` property for "New file every day", which only works when
the `{day}` is in the path string. When auto-logging with this setting, opens
a new file at midnight and starts writing that one.
<!-- * [ ] `LoggingSettings` property for "Flush log frequently", defaults to
`true`(?). This causes us to flush all output to the file, instead of just...
on close? on newline? It's unclear exactly when PuTTY flushes with this off.
Need more coffee. -->
### Future Considerations
* When logging begins, the Terminal could display a toast for "Logging to
{filename}", and a similar one for "Stopped logging to {filename}".
* There's no good way of displaying a UI element to indicate that a pane is
currently logging to a file. I don't believe PuTTY displays any sort of
indicator. SecureCRT only displays a checkbox within the context menus of the
application itself.
![SecureCRT context menu](SecureCRT-context-menu.png)
Maybe when logging to a file, we could replace the "Export Text" context menu
entry with "Stop Logging"
* We could maybe add a setting to disable logging from the alt buffer. This
might help make this setting more valuable for users who are using full-screen
applications like `vim`. Since those applications redraw the entire viewport
contents frequently, the log might be unnecessarily noisy. Disabling logging
while in the alt buffer would show that the user opened vim, and then they did
some things after vim exited.
* Logging all output will be VERY helpful to us in the future for trying to
recreate bugs on our end that users can repro but we can't!
## Resources
PuTTY Logging documentation: https://tartarus.org/~simon/putty-snapshots/htmldoc/Chapter4.html#config-logfilename
ConEmu Logging documentation: https://conemu.github.io/en/AnsiLogFiles.html
### Footnotes
<a name="footnote-1"><a>[1]: Remember that `^H` is non-destructive, so the
sequence `what^h ^h^h ^h` is can be read as:
* print "what"
* move the cursor back one
* print a space (overwriting 't')
* move the cursor back one (now it's on the space where 't' was)
* move the cursor back one
* print a space (overwriting 'a')
* move the cursor back one (now it's on the space where 'a' was)
[#642]: https://github.com/microsoft/terminal/issues/642
[#5000]: https://github.com/microsoft/terminal/issues/5000
[#9287]: https://github.com/microsoft/terminal/pull/9287
[#11045]: https://github.com/microsoft/terminal/pull/11045
[#11062]: https://github.com/microsoft/terminal/pull/11062

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -60,7 +60,7 @@ it could host any arbitrary content.
### Security
I don't foresee this implementation by itself raising security concerns. This
I don't forsee this implementation by itself raising security concerns. This
feature is mostly concerned with adding support for arbitrary controls, not
actually implementing some arbitrary controls.

View File

@@ -1,217 +0,0 @@
# Windows Terminal Accessibility
## Abstract
The release of Windows Terminal has served as a way to reinvigorate the command-line ecosystem on Windows by providing a modern experience that is consistently updated. This experience has caused faster innovation in the Windows command-line ecosystem that can be seen across various areas like expanded virtual terminal sequence support and enhanced accessibility. Command-line apps can now leverage these innovations to create a better experience for the end-user.
Since accessibility is a very broad area, this document is intended to present recent innovations in the accessibility space for the command-line ecosystem. Furthermore, it will propose additional improvements that can be made alongside key stakeholders that could benefit from such changes.
## Windows Command-line Ecosystem
### First-party terminals
For many years, Console Host (Conhost) was the only first-party terminal on Windows. In 2019, Windows Terminal was released to the world as an open source first-party terminal. Windows Terminal was distributed through the Microsoft Store and received regular updates throughout the year, much more frequently than Conhost. In October 2022, Windows Terminal was enabled as the default terminal on Windows.
A significant amount of code is shared between Conhost and Windows Terminal to create the terminal area. To enable an accessible experience for this area, a shared UI Automation provider was introduced in 2019[^1], enabling accessibility tools to navigate and read contents from the terminal area. In 2020, Windows Terminal was updated to dispatch UIA events signaling when the cursor position, text output, or selection changed; this left the work of identifying what changed in the output to the attached screen reader application[^2]. In 2022, Windows Terminal was updated to dispatch UIA notifications with a payload of what text was written to the screen[^3].
### Internal Partners
There are many first-party command-line applications on Windows. The following are a few examples of those that are regularly updated:
- [**GitHub CLI**](https://cli.github.com/): a tool that can be used to query and interact with GitHub repos (open source)
- [**Winget**](https://github.com/microsoft/winget-cli): a tool to install applications and other packages
- [**PSReadLine**](https://github.com/PowerShell/PSReadLine): a PowerShell module that enhances the input line experience
- [**Windows Subsystem for Linux (WSL)**](https://learn.microsoft.com/en-us/windows/wsl/): a tool to manage and run GNU/Linux environments without a traditional virtual machine
- [**PowerShell**](https://github.com/PowerShell/PowerShell): a cross-platform command-line shell (open source)
Additionally, CMD is a Windows-specific command-line shell. Though it is no longer being updated, it is still widely used.
### External Command-line Applications
There are many third-party command-line applications on Windows that are regularly updated.
The following examples take over the entire viewport:
- [**Vim**](https://www.vim.org/): a modal-based text editor with an extensive plugin system
- **Emacs**: a shortcut-oriented text editor with an extensive plugin system
- [**Emacspeak**](https://emacspeak.sourceforge.net/): an Emacs subsystem that leverages Emacs' context-specific information to produce speech output
- **Midnight Commander**: a file manager with an extensive text user interface
- **Far Manager**: a file manager for Windows with an exclusive text user interface
- **tmux**: a terminal multiplexer
The following examples don't take over the entire viewport:
- [**Oh My Posh**](https://ohmyposh.dev/): a tool to customize shell prompts
- **git**: a tool for version control
The following examples operate as command-line shells:
- [**Bash**](https://www.gnu.org/software/bash/) is the default shell for most Linux distributions
- [**Fish shell**](https://fishshell.com/) provides a rich shell experience with features like autosuggestion support and VGA colors
- [**Z shell**](https://zsh.sourceforge.io/) is an extended Bourne shell
## Accessibility tools and features
### Screen Readers
Windows is predominantly dominated by three major screen readers: Narrator, JAWS, and NVDA.
- **Narrator**: a free first-party screen reader that ships with Windows.
- **JAWS**: a third-party screen reader that requires an annual license to use.
- **NVDA**: a free third-party screen reader that is open source.
It's important to note that most users generally use NVDA or JAWS[^4], so it's very important to consider the experience under both of those screen readers.
### Miscellaneous User Tools
Windows has many built-in features that enable a more accessible experiences. The following is a list of Windows accessibility settings and tools that may have an impact on apps:
- **Text size setting**: dictates the default size of text throughout all of Windows
- **Always show scrollbars setting**: forces the scrollbars to always be expanded and shown. Generally, this setting is automatically respected when using the Windows UI library.
- **Transparency effects setting**: allows transparency effects within the Windows shell
- **Animation effects setting**: allows animation effects
- **Text cursor indicator**: displays a colorful UI over the text cursor to make it stand out more and easier to find. Note, this is powered by UI Automation selection changed events because an empty selection is the cursor's position.
- **Magnifier**: zooms the monitor to provide a closer look at the current mouse and cursor position. Note, this is powered by UI Automation selection changed events because an empty selection is the cursor's position.
- **Color filters**: manipulates displayed colors to address color-blindness
- **High contrast themes**: ensures all displayed content has a high contrast ratio. Generally, this setting is automatically respected when using the Windows UI library. However, non-WinUI surfaces (like the terminal area) need to do additional work to respect this.
- **Voice access**: enables users to interact with their PC using their voice. This is powered by UI Automation to identify what controls can be interacted with and how.
### Verification Tools
[Accessibility Insights](https://accessibilityinsights.io/) is a modern tool that can be used to test and understand accessibility issues on Windows. It comes with a built-in color contrast analyzer, a UI Automation event listener, and a UI Automation tree explorer. Additionally, it can be used to explore and interact with different control patterns using the same API screen readers and other accessibility tools rely on. Accessibility Insights is also capable of running automated tests in CI and locally to detect common and simple issues.
## Proposed Accessibility Improvements
### Respect Text Size OS setting
Windows Terminal has a profile setting for the font size (`"font"."size": <integer>`) which is then used when instantiating new terminal sessions. There should be a way to make the terminal font size respect the text size setting in Windows found in the Settings App > Accessibility > Text Size.
A possible solution is to scale the session's text size at runtime based on the OS setting. Furthermore, the Windows Terminal's settings UI should warn the user when they are attempting to modify the font size profile setting.
This is tracked by [Windows accessibility "text size" parameter ignored by terminal · Issue #13600](https://github.com/microsoft/terminal/issues/13600)
### Respect High Contrast Mode
When high contrast mode is enabled, any WinUI surfaces are automatically changed to respect the newly desired contrast mode. However, the terminal area does not. In 2021, the Delta E algorithm was added to Windows Terminal as a tool to improve color contrast in the terminal area if desired[^5]. In 2022, it was exposed via an `adjustIndistinguishableColors` profile setting with a variety of possible configurations[^6].
There are several possible solutions to this issue, which all should be exposed to the user. Such solutions include the following:
- Enabling `adjustIndistinguishableColors` automatically when high contrast mode is enabled
- This leverages the Delta E algorithm to force an adequate contrast ratio. The algorithm would need to be expanded to expose the threshold.
- Reducing the colors used to match the Windows Contrast Theme colors
- Edge uses this heuristic and it has the added benefit that Windows Terminal is respecting the user's high contrast theme set in the OS.
- Implementing the `DECSTGLT` escape sequence which enables switching between different color modes (one of which is monochrome)
Additionally, we should automatically make the terminal fully opaque and ignore the acrylic or traditional transparency setting.
This is tracked by [[Epic] Improved High Contrast support · Issue #12999](https://github.com/microsoft/terminal/issues/12999)
### Enhanced UI Automation movement mechanisms
UI Automation has an `ITextRangeProvider` interface that can be used to represent a span of text. These text ranges can then be manipulated to explore the text area. Such manipulations include moving either (or both) endpoint(s) to encompass a text unit or move by unit. UI Automation supports the following text units:
- Character
- Format
- Word
- Line
- Paragraph
- Page
- Document
Windows Terminal and Conhost implement this `ITextRangeProvider` interface. However, this implementation doesn't support all of the available text units. Though this is standard across other implementations, this provides an opportunity for growth.
#### UI Automation: Movement by page
Movement by page is a relatively abstract concept in terminals. Arguably, the viewport could be leveraged to be considered a "page", which would provide users with a quick way to navigate the buffer.
This is tracked by [UIA: support movement by page · Issue #13756](https://github.com/microsoft/terminal/issues/13756)
#### UI Automation: Movement by format
The terminal area supports various text decorations including but not limited to underlined, bold, and italic text. These text attributes were exposed via the `ITextRangeProvider` interface in [PR #10366](https://github.com/microsoft/terminal/pull/10336). UI automation has support for navigating through contiguous spans adhering to a text attribute, which could be useful to expose to UIA clients.
This is tracked by [UIA Formatted Text Navigation · Issue #3567](https://github.com/microsoft/terminal/issues/3567).
#### Movement by prompt
[PR #12948](https://github.com/microsoft/terminal/pull/12948) added support for scrollbar marks in Windows Terminal. As a part of this, an `experimental.autoMarkPrompts` profile setting registers scrollbar marks with each prompt when enabled. This is a fantastic way to navigate through the scroll history quickly and easily.
Though UI automation doesn't have a text unit for prompts, we could define a "paragraph" as the space between two prompts.
This issue is tracked by [megathread: Scrollbar Marks · Issue #11000](https://github.com/microsoft/terminal/issues/11000).
### Mark Mode support for degenerate range
[PR #13053](https://github.com/microsoft/terminal/pull/13053) added support for mark mode in Windows Terminal. Mark mode allows users to create and modify selections by exclusively using the keyboard. However, screen reader users have reported it as a strange experience because it always has a cell of text selected; this results in the screen reader reading "x selected, y unselected" as opposed to the expected "x" when moving the cursor around.
Unfortunately, the changes required to fix this are very extensive because selections are stored as two inclusive terminal coordinates, which makes it impossible to represent an empty selection.
This is tracked by [A11y: windows terminal emits selection/deselection events in mark mode when navigating with arrow keys · Issue #13447](https://github.com/microsoft/terminal/issues/13447).
### Search improvements
The search dialog can be used to perform a runtime query of the text buffer. However, in its current form, it does not count the total number of matches like other developer tools (i.e. Visual Studio Code). This work item tracks two things:
1. Support for counting the total number of matches
2. Displaying the number of results in the search box and announcing it via a UIA notification to the screen reader
Additional search features may fall into this including but not limited to:
- Highlight the search results in the scroll bar
- Successful searches should read the line where text was found (similar to VS Code experience)
It seems that UI Automation has no guidance for a consistent, good search experience on Windows. Noting that VS Code and Microsoft Word have two different search experiences, it may be valuable to create documentation and guidance for other developer tools on Windows to follow, thus helping to create a more consistent search experience.
This is tracked by [Search should display the number of results it finds · Issue #6319](https://github.com/microsoft/terminal/issues/6319) and [[JAWS] Search results read "results found" instead of something useful · Issue #14153](https://github.com/microsoft/terminal/issues/14153).
### Shell suggestions
Shells provide different forms of autocompletion. However, that autocompletion isn't necessarily an accessible experience. For example, CMD's standard autocompletion experience rewrites the "word". The screen reader rereads the new "word" properly, however, the user must cycle between the options until the desired one is shown. As another example,
PowerShell supports menu completion, where possible completions are displayed as a table of text and relevant help text is displayed. However, screen readers struggle with this because the entire menu is redrawn every time, making it harder to understand what exactly is "selected" (as the concept of selection in this instance is a shell-side concept represented by visual manipulation).
A possible solution is to introduce a new VT sequence to have the shell provide a payload. This payload can contain things like the suggested completion as well as associated help text. Such data can then be leveraged by Windows Terminal to create UI elements. Doing so leverages WinUI's accessible design. By designing a new VT sequence, other terminal emulators and CLI apps can opt-in to this functionality too.
This is tracked by [Enhance shell autocompletion with a cool new user interface and shell completion protocol · Issue #3121](https://github.com/microsoft/terminal/issues/3121).
### Scripts Panel
Common command-line experiences revolve around inputting a command into the shell and executing it. Expert command-line users can remember complex commands off the top of their heads. Windows Terminal could help users by storing complex commands. Furthermore, these commands should be able to be easily shared between users.
From an accessibility standpoint, this can be very useful for users with mobility issues, as it is particularly difficult to write complex commands quickly.
This is tracked by [Feature Request - Scripts Panel · Issue #1595](https://github.com/microsoft/terminal/issues/1595).
### Broadcast Input
Windows Terminal supports running multiple sessions at once across various panes, tabs, and windows. The broadcast input feature allows users to send input to multiple sessions at once.
From an accessibility standpoint, this can be very useful for users with mobility issues, as it is time consuming to write commands to multiple sessions quickly, particularly if those commands are complex.
This is tracked by [Support broadcast input? · Issue #2634](https://github.com/microsoft/terminal/issues/2634).
### UI Automation Notification Related Improvements
In 2022, Windows Terminal added UI Automation notifications that contained a payload of text output[^3]. This was done for various reasons. For one, Narrator was not reading new output by the terminal, and would require changes on their end to accomplish this. Another reason is that NVDA is unable to handle too many text changed events, which Conhost and Windows Terminal are both culprits of since they dispatch an event when text is written to the terminal output.
UIA notifications have provided many compatibility benefits since screen readers automatically read notifications they receive. Additionally, this has provided the possibility for major performance enhancements as screen readers may no longer be required to diff the text buffer and figure out what has changed. NVDA has prototyped listening to notifications and ignoring text changed events entirely[^7]. However, it reveals underlying challenges with this new model such as how to handle passwords. The proposals listed in this section are intended to have Windows Terminal achieve improved performance and accessibility quality.
#### VT Screen Reader Control
Some command-line applications are simply too difficult to create a consistent accessible experience. Applications that draw decorative content, for example, may have that content read by a screen reader.
In 2019, Daniel Imms wrote a spec proposing a VT sequence that can partially control the attached screen reader[^8]. This VT sequence consists of three main formats:
1. Stop announcing incoming data to the screen reader. The screen reader will resume announcing incoming data if any key is pressed.
2. Resume announcing incoming data to the screen reader.
3. Announce the associated string payload immediately.
Additionally, all three formats include a string payload that will be announced immediately by the screen reader (as is done with the third format).
JAWS and Narrator both immediately read UIA notifications as that is how Windows Terminal presents newly output text. As described earlier, NVDA currently has notifications disabled, but is prototyping moving towards a world where they can drop text diffing entirely in favor of this.
With Windows Terminal now dispatching UIA notifications, it would be relatively trivial for Windows Terminal to extract the string payload from the relevant VT sequences and present it in a new UIA notification. Additionally, an internal flag would be enabled to suppress UIA notifications for text output until the flag is flipped via a key press or the relevant VT sequence is received.
This is tracked by [Unable to use applications that hook the arrow keys using Windows Console Host. · Issue #13666](https://github.com/microsoft/terminal/issues/13666)
#### Screen Reader Backpressure Control Proposal
NVDA has reported issues where Windows Terminal sends too may text changed events at once, causing NVDA to hang. Several considerations have been made in this area to address this issue:
- **Batch notifications**: unhelpful because Terminal doesn't know how much text is intended to be output
- **Diff text before sending a notification**: same problem as above
- **Provide an API to throttle notifications**: causes a security risk for denial of service attacks
Surprisingly, UI Automation doesn't have a built-in way to handle backpressure. However, the appropriate fix here seems to be either on the UI Automation side to fix this for all UIA clients, or on the NVDA side to improve event handling.
This is tracked by [UIA: reduce number of extraneous text change events in conhost · Issue #10822](https://github.com/microsoft/terminal/issues/10822)
## Proposed Priorities
The following table assigns priorities to the aforementioned work items. These priorities are solely based on accessibility impact to improve the Windows command-line ecosystem.
| Priority | Work Item | Reasoning |
|----------|-----------|-----------|
| 1 | VT Screen Reader Control | Several partners (both CLI apps and UIA clients) would benefit from this feature and have expressed interest in adopting it. |
| 1 | Shell suggestions | Several partners (both CLI apps and UIA clients) would benefit from this feature and have expressed interest in adopting it. |
| 1 | Search improvements | Search is very difficult to use in its current implementation. |
| 1 | Mark Mode support for degenerate range | Experience is barely usable and very unfriendly for non-sighted users. This is the only non-mouse method to select text. |
| 2 | Respect High Contrast Mode | Clear workarounds exist, but the golden path scenario fails where a user enables high contrast mode and content does not respect it. |
| 2 | Text Size OS Setting | Clear workarounds exist, but the golden path scenario fails where a user enables an OS setting and content does not respect it. |
| 2 | Broadcast Input | Users with mobility issues would greatly benefit from this. |
| 3 | Scripts Panel | Send-input commands currently exist. This would just expose it better. |
| 3 | UIA: Move to prompt | Additional UIA text unit support is good. |
| 3 | UIA: Move by page | Additional UIA text unit support is good. |
| 3 | UIA: Move by format | Additional UIA text unit support is good. |
Generally, the reasoning behind these priorities can be broken down as follows:
- Priority 1 bucket:
- These work items provide access to features within the command-line ecosystem that are practically unusable for users with accessibility needs.
- Priority 2 bucket:
- Active workarounds exist for these issues, but they could be cumbersome.
- Priority 3 bucket:
- Purely innovative ideas that provide an exceptional experience for users.
## References
[^1]: [Accessibility: Set-up UIA Tree by carlos-zamora · Pull Request #1691](https://github.com/microsoft/terminal/pull/1691)
[^2]: [Fire UIA Events for Output and Cursor Movement by carlos-zamora · Pull Request #4826](https://github.com/microsoft/terminal/pull/4826)
[^3]: [Use UIA notifications for text output by carlos-zamora · Pull Request #12358](https://github.com/microsoft/terminal/pull/12358)
[^4]: [WebAIM: Screen Reader User Survey #8 Results](https://webaim.org/projects/screenreadersurvey8/#:~:text=NVDA%20is%20now%20the%20most%20commonly%20used%20screen,respondents%20use%20more%20than%20one%20desktop%2Flaptop%20screen%20reader.)
[^5]: [Implement the Delta E algorithm to improve color perception by PankajBhojwani · Pull Request #11095](https://github.com/microsoft/terminal/pull/11095)
[^6]: [Change AdjustIndistinguishableColors to an enum setting instead of a boolean setting by PankajBhojwani · Pull Request #13512](https://github.com/microsoft/terminal/pull/13512)
[^7]: [Prototype for Windows Terminal: Use notifications instead of monitoring for new text by leonardder · Pull Request #14047 · nvaccess/nvda (github.com)](https://github.com/nvaccess/nvda/pull/14047)
[^8]: [Control Screen Reader from Applications (#18) · Issues · terminal-wg / specifications · GitLab](https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/18)

2
gitbranch.cmd Normal file
View File

@@ -0,0 +1,2 @@
@echo off
git branch | D:\dev\private\OpenConsole\bin\x64\Debug\Scratch.exe --prefix "git checkout "

View File

@@ -12,12 +12,19 @@ using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Navigation;
namespace xaml = ::winrt::Windows::UI::Xaml;
namespace winrt::SampleApp::implementation
{
App::App()
{
// This is the same trick that Initialize() is about to use to figure out whether we're coming
// from a UWP context or from a Win32 context
// See https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/blob/52611c57d89554f357f281d0c79036426a7d9257/Microsoft.Toolkit.Win32.UI.XamlApplication/XamlApplication.cpp#L42
const auto dispatcherQueue = ::winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (dispatcherQueue)
{
_isUwp = true;
}
Initialize();
// Disable XAML's automatic backplating of text when in High Contrast
@@ -26,44 +33,6 @@ namespace winrt::SampleApp::implementation
HighContrastAdjustment(::winrt::Windows::UI::Xaml::ApplicationHighContrastAdjustment::None);
}
void App::Initialize()
{
const auto dispatcherQueue = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (!dispatcherQueue)
{
_windowsXamlManager = xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
}
else
{
_isUwp = true;
}
}
void App::Close()
{
if (_bIsClosed)
{
return;
}
_bIsClosed = true;
if (_windowsXamlManager)
{
_windowsXamlManager.Close();
}
_windowsXamlManager = nullptr;
Exit();
{
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
::DispatchMessageW(&msg);
}
}
}
SampleAppLogic App::Logic()
{
static SampleAppLogic logic;

View File

@@ -12,22 +12,12 @@ namespace winrt::SampleApp::implementation
{
public:
App();
void Initialize();
void Close();
void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&);
bool IsDisposed() const
{
return _bIsClosed;
}
SampleApp::SampleAppLogic Logic();
private:
bool _isUwp = false;
winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager _windowsXamlManager = nullptr;
winrt::Windows::Foundation::Collections::IVector<winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider> _providers = winrt::single_threaded_vector<Windows::UI::Xaml::Markup::IXamlMetadataProvider>();
bool _bIsClosed = false;
};
}

View File

@@ -7,12 +7,10 @@ namespace SampleApp
{
// ADD ARBITRARY APP LOGIC TO SampleAppLogic.idl, NOT HERE.
// This is for XAML platform setup only.
[default_interface] runtimeclass App : Windows.UI.Xaml.Application, Windows.Foundation.IClosable
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
{
App();
SampleAppLogic Logic { get; };
Boolean IsDisposed { get; };
}
}

View File

@@ -2,19 +2,20 @@
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Application x:Class="SampleApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:SampleApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:SampleApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Toolkit:XamlApplication x:Class="SampleApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:SampleApp"
xmlns:Toolkit="using:Microsoft.Toolkit.Win32.UI.XamlHost"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:SampleApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--
If you want to prove this works, then add `RequestedTheme="Light"` to
the properties on the XamlApplication
-->
<Application.Resources>
<Toolkit:XamlApplication.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -66,5 +67,5 @@
</ResourceDictionary>
</Application.Resources>
</Application>
</Toolkit:XamlApplication.Resources>
</Toolkit:XamlApplication>

View File

@@ -20,6 +20,7 @@
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
@@ -127,16 +128,6 @@
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="$(WindowsSDK_MetadataPathVersioned)\Windows.UI.Xaml.Hosting.HostingContract\*\*.winmd">
<WinMDFile>true</WinMDFile>
<CopyLocal>false</CopyLocal>
<ReferenceGrouping>$(TargetPlatformMoniker)</ReferenceGrouping>
<ReferenceGroupingDisplayName>$(TargetPlatformDisplayName)</ReferenceGroupingDisplayName>
<ResolvedFrom>CppWinRTImplicitlyExpandTargetPlatform</ResolvedFrom>
<IsSystemReference>True</IsSystemReference>
</Reference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
@@ -167,7 +158,7 @@
<!--
By default, the PRI file will contain resource paths beginning with the
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
Well, App.xbf is hard-coded by the framework to be found at the resource ROOT.
Well, App.xbf is hardcoded by the framework to be found at the resource ROOT.
To make that happen, we have to disable the prepending of the project name
to the App xaml files.
-->

View File

@@ -13,6 +13,7 @@
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
@@ -44,7 +45,7 @@
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<!-- Reference SampleAppLib here, so we can use its App.winmd as
<!-- Reference SampleAppLib here, so we can use it's App.winmd as
our App.winmd. This didn't work correctly in VS2017, you'd need to
manually reference the lib -->
<ProjectReference Include="$(OpenConsoleDir)scratch\ScratchIslandApp\SampleApp\SampleAppLib.vcxproj">

View File

@@ -46,6 +46,7 @@
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.ViewManagement.h"
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

View File

@@ -90,7 +90,7 @@
Particularly tricky is Microsoft.Terminal.Core.winmd. That winmd doesn't
have its own DLL (it doesn't have any activatable classes, only structs and
interfaces). However, it too is necessary for Terminal.Control to be able to
marshal the Core types across the boundary.
marshall the Core types across the boundary.
-->
<Reference Include="Microsoft.Terminal.Core">
<HintPath>$(OpenConsoleCommonOutDir)TerminalCore\Microsoft.Terminal.Core.winmd</HintPath>

View File

@@ -25,19 +25,19 @@ constexpr til::inclusive_rect ScreenToBufferLine(const til::inclusive_rect& line
{
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.left >> scale, line.top, line.right >> scale, line.bottom };
return { line.Left >> scale, line.Top, line.Right >> scale, line.Bottom };
}
constexpr til::point ScreenToBufferLine(const til::point& line, const LineRendition lineRendition)
{
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.x >> scale, line.y };
return { line.X >> scale, line.Y };
}
constexpr til::inclusive_rect BufferToScreenLine(const til::inclusive_rect& line, const LineRendition lineRendition)
{
// Use shift left to quickly multiply the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.left << scale, line.top, (line.right << scale) + scale, line.bottom };
return { line.Left << scale, line.Top, (line.Right << scale) + scale, line.Bottom };
}

View File

@@ -5,9 +5,8 @@
#include "OutputCellIterator.hpp"
#include <til/unicode.h>
#include "../../types/inc/convert.hpp"
#include "../../types/inc/Utf16Parser.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include "../../inc/conattrs.hpp"
@@ -82,7 +81,7 @@ OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t f
// - This is an iterator over a range of text only. No color data will be modified as the text is inserted.
// Arguments:
// - utf16Text - UTF-16 text range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) noexcept :
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
_mode(Mode::LooseTextOnly),
_currentView(s_GenerateView(utf16Text)),
_run(utf16Text),
@@ -98,7 +97,7 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) noexce
// Arguments:
// - utf16Text - UTF-16 text range
// - attribute - Color to apply over the entire range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit) noexcept :
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit) :
_mode(Mode::Loose),
_currentView(s_GenerateView(utf16Text, attribute)),
_run(utf16Text),
@@ -357,7 +356,7 @@ bool OutputCellIterator::_TryMoveTrailing() noexcept
// - view - View representing characters corresponding to a single glyph
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view) noexcept
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
{
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
}
@@ -372,7 +371,8 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
// - attr - Color attributes to apply to the text
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr)
{
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
}
@@ -388,9 +388,11 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
// - behavior - Behavior of the given text attribute (used when writing)
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior)
{
const auto glyph = til::utf16_next(view);
const auto glyph = Utf16Parser::ParseNext(view);
const auto dbcsAttr = IsGlyphFullWidth(glyph) ? DbcsAttribute::Leading : DbcsAttribute::Single;
return OutputCellView(glyph, dbcsAttr, attr, behavior);
}

View File

@@ -37,8 +37,8 @@ public:
OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const std::wstring_view utf16Text) noexcept;
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const std::wstring_view utf16Text);
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit = 0);
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
OutputCellIterator(const gsl::span<const OutputCell> cells);
@@ -100,9 +100,15 @@ private:
bool _TryMoveTrailing() noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior);
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;

View File

@@ -101,13 +101,6 @@ void Cursor::SetIsOn(const bool fIsOn) noexcept
void Cursor::SetBlinkingAllowed(const bool fBlinkingAllowed) noexcept
{
_fBlinkingAllowed = fBlinkingAllowed;
// GH#2642 - From what we've gathered from other terminals, when blinking is
// disabled, the cursor should remain On always, and have the visibility
// controlled by the IsVisible property. So when you do a printf "\e[?12l"
// to disable blinking, the cursor stays stuck On. At this point, only the
// cursor visibility property controls whether the user can see it or not.
// (Yes, the cursor can be On and NOT Visible)
_fIsOn = true;
_RedrawCursorAlways();
}
@@ -206,7 +199,7 @@ void Cursor::SetPosition(const til::point cPosition) noexcept
void Cursor::SetXPosition(const til::CoordType NewX) noexcept
{
_RedrawCursor();
_cPosition.x = NewX;
_cPosition.X = NewX;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -214,7 +207,7 @@ void Cursor::SetXPosition(const til::CoordType NewX) noexcept
void Cursor::SetYPosition(const til::CoordType NewY) noexcept
{
_RedrawCursor();
_cPosition.y = NewY;
_cPosition.Y = NewY;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -222,7 +215,7 @@ void Cursor::SetYPosition(const til::CoordType NewY) noexcept
void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
{
_RedrawCursor();
_cPosition.x += DeltaX;
_cPosition.X += DeltaX;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -230,7 +223,7 @@ void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
{
_RedrawCursor();
_cPosition.y += DeltaY;
_cPosition.Y += DeltaY;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -238,7 +231,7 @@ void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
{
_RedrawCursor();
_cPosition.x -= DeltaX;
_cPosition.X -= DeltaX;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -246,7 +239,7 @@ void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
void Cursor::DecrementYPosition(const til::CoordType DeltaY) noexcept
{
_RedrawCursor();
_cPosition.y -= DeltaY;
_cPosition.Y -= DeltaY;
_RedrawCursor();
ResetDelayEOLWrap();
}

View File

@@ -5,9 +5,8 @@
#include "search.h"
#include <til/unicode.h>
#include "textBuffer.hpp"
#include "../types/inc/Utf16Parser.hpp"
#include "../types/inc/GlyphWidth.hpp"
using namespace Microsoft::Console::Types;
@@ -18,19 +17,19 @@ using namespace Microsoft::Console::Types;
// - Once you've found something, you can perform actions like .Select() or .Color()
// Arguments:
// - textBuffer - The screen text buffer to search through (the "haystack")
// - renderData - The IRenderData type reference, it is for providing selection methods
// - uiaData - The IUiaData type reference, it is for providing selection methods
// - str - The search term you want to find (the "needle")
// - direction - The direction to search (upward or downward)
// - sensitivity - Whether or not you care about case
Search::Search(Microsoft::Console::Render::IRenderData& renderData,
Search::Search(IUiaData& uiaData,
const std::wstring_view str,
const Direction direction,
const Sensitivity sensitivity) :
_direction(direction),
_sensitivity(sensitivity),
_needle(s_CreateNeedleFromString(str)),
_renderData(renderData),
_coordAnchor(s_GetInitialAnchor(renderData, direction))
_uiaData(uiaData),
_coordAnchor(s_GetInitialAnchor(uiaData, direction))
{
_coordNext = _coordAnchor;
}
@@ -41,12 +40,12 @@ Search::Search(Microsoft::Console::Render::IRenderData& renderData,
// - Once you've found something, you can perform actions like .Select() or .Color()
// Arguments:
// - textBuffer - The screen text buffer to search through (the "haystack")
// - renderData - The IRenderData type reference, it is for providing selection methods
// - uiaData - The IUiaData type reference, it is for providing selection methods
// - str - The search term you want to find (the "needle")
// - direction - The direction to search (upward or downward)
// - sensitivity - Whether or not you care about case
// - anchor - starting search location in screenInfo
Search::Search(Microsoft::Console::Render::IRenderData& renderData,
Search::Search(IUiaData& uiaData,
const std::wstring_view str,
const Direction direction,
const Sensitivity sensitivity,
@@ -55,7 +54,7 @@ Search::Search(Microsoft::Console::Render::IRenderData& renderData,
_sensitivity(sensitivity),
_needle(s_CreateNeedleFromString(str)),
_coordAnchor(anchor),
_renderData(renderData)
_uiaData(uiaData)
{
_coordNext = _coordAnchor;
}
@@ -99,10 +98,10 @@ void Search::Select() const
{
// Convert buffer selection offsets into the equivalent screen coordinates
// required by SelectNewRegion, taking line renditions into account.
const auto& textBuffer = _renderData.GetTextBuffer();
const auto& textBuffer = _uiaData.GetTextBuffer();
const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart);
const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd);
_renderData.SelectNewRegion(selStart, selEnd);
_uiaData.SelectNewRegion(selStart, selEnd);
}
// Routine Description:
@@ -114,7 +113,7 @@ void Search::Color(const TextAttribute attr) const
// Only select if we've found something.
if (_coordSelEnd >= _coordSelStart)
{
_renderData.ColorSelection(_coordSelStart, _coordSelEnd, attr);
_uiaData.ColorSelection(_coordSelStart, _coordSelEnd, attr);
}
}
@@ -135,19 +134,19 @@ std::pair<til::point, til::point> Search::GetFoundLocation() const noexcept
// - If the screen buffer given already has a selection in it, it will be used to determine the anchor.
// - Otherwise, we will choose one of the ends of the screen buffer depending on direction.
// Arguments:
// - renderData - The reference to the IRenderData interface type object
// - uiaData - The reference to the IUiaData interface type object
// - direction - The intended direction of the search
// Return Value:
// - Coordinate to start the search from.
til::point Search::s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction direction)
til::point Search::s_GetInitialAnchor(const IUiaData& uiaData, const Direction direction)
{
const auto& textBuffer = renderData.GetTextBuffer();
const auto textBufferEndPosition = renderData.GetTextBufferEndPosition();
if (renderData.IsSelectionActive())
const auto& textBuffer = uiaData.GetTextBuffer();
const auto textBufferEndPosition = uiaData.GetTextBufferEndPosition();
if (uiaData.IsSelectionActive())
{
// Convert the screen position of the selection anchor into an equivalent
// buffer position to start searching, taking line rendition into account.
auto anchor = textBuffer.ScreenToBufferPosition(renderData.GetSelectionAnchor());
auto anchor = textBuffer.ScreenToBufferPosition(uiaData.GetSelectionAnchor());
if (direction == Direction::Forward)
{
@@ -158,8 +157,8 @@ til::point Search::s_GetInitialAnchor(const Microsoft::Console::Render::IRenderD
textBuffer.GetSize().DecrementInBoundsCircular(anchor);
// If the selection starts at (0, 0), we need to make sure
// it does not exceed the text buffer end position
anchor.x = std::min(textBufferEndPosition.x, anchor.x);
anchor.y = std::min(textBufferEndPosition.y, anchor.y);
anchor.X = std::min(textBufferEndPosition.X, anchor.X);
anchor.Y = std::min(textBufferEndPosition.Y, anchor.Y);
}
return anchor;
}
@@ -193,11 +192,12 @@ bool Search::_FindNeedleInHaystackAt(const til::point pos, til::point& start, ti
auto bufferPos = pos;
for (const auto& needleChars : _needle)
for (const auto& needleCell : _needle)
{
// Haystack is the buffer. Needle is the string we were given.
const auto hayIter = _renderData.GetTextBuffer().GetTextDataAt(bufferPos);
const auto hayIter = _uiaData.GetTextBuffer().GetTextDataAt(bufferPos);
const auto hayChars = *hayIter;
const auto needleChars = std::wstring_view(needleCell.data(), needleCell.size());
// If we didn't match at any point of the needle, return false.
if (!_CompareChars(hayChars, needleChars))
@@ -269,7 +269,7 @@ wchar_t Search::_ApplySensitivity(const wchar_t wch) const noexcept
// - coord - Updated by function to increment one position (will wrap X and Y direction)
void Search::_IncrementCoord(til::point& coord) const noexcept
{
_renderData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
_uiaData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
}
// Routine Description:
@@ -278,7 +278,7 @@ void Search::_IncrementCoord(til::point& coord) const noexcept
// - coord - Updated by function to decrement one position (will wrap X and Y direction)
void Search::_DecrementCoord(til::point& coord) const noexcept
{
_renderData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
_uiaData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
}
// Routine Description:
@@ -305,10 +305,10 @@ void Search::_UpdateNextPosition()
// We put the next position to:
// Forward: (0, 0)
// Backward: the position of the end of the text buffer
const auto bufferEndPosition = _renderData.GetTextBufferEndPosition();
const auto bufferEndPosition = _uiaData.GetTextBufferEndPosition();
if (_coordNext.y > bufferEndPosition.y ||
(_coordNext.y == bufferEndPosition.y && _coordNext.x > bufferEndPosition.x))
if (_coordNext.Y > bufferEndPosition.Y ||
(_coordNext.Y == bufferEndPosition.Y && _coordNext.X > bufferEndPosition.X))
{
if (_direction == Direction::Forward)
{
@@ -328,12 +328,13 @@ void Search::_UpdateNextPosition()
// - wstr - String that will be our search term
// Return Value:
// - Structured text data for comparison to screen buffer text data.
std::vector<std::wstring> Search::s_CreateNeedleFromString(const std::wstring_view wstr)
std::vector<std::vector<wchar_t>> Search::s_CreateNeedleFromString(const std::wstring_view wstr)
{
std::vector<std::wstring> cells;
for (const auto& chars : til::utf16_iterator{ wstr })
const auto charData = Utf16Parser::Parse(wstr);
std::vector<std::vector<wchar_t>> cells;
for (const auto chars : charData)
{
if (IsGlyphFullWidth(chars))
if (IsGlyphFullWidth(std::wstring_view{ chars.data(), chars.size() }))
{
cells.emplace_back(chars);
}

View File

@@ -17,9 +17,10 @@ Revision History:
#pragma once
#include <WinConTypes.h>
#include "TextAttribute.hpp"
#include "textBuffer.hpp"
#include "../renderer/inc/IRenderData.hpp"
#include "../types/IUiaData.h"
// This used to be in find.h.
#define SEARCH_STRING_LENGTH (80)
@@ -39,12 +40,12 @@ public:
CaseSensitive
};
Search(Microsoft::Console::Render::IRenderData& renderData,
Search(Microsoft::Console::Types::IUiaData& uiaData,
const std::wstring_view str,
const Direction dir,
const Sensitivity sensitivity);
Search(Microsoft::Console::Render::IRenderData& renderData,
Search(Microsoft::Console::Types::IUiaData& uiaData,
const std::wstring_view str,
const Direction dir,
const Sensitivity sensitivity,
@@ -65,9 +66,9 @@ private:
void _IncrementCoord(til::point& coord) const noexcept;
void _DecrementCoord(til::point& coord) const noexcept;
static til::point s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction dir);
static til::point s_GetInitialAnchor(const Microsoft::Console::Types::IUiaData& uiaData, const Direction dir);
static std::vector<std::wstring> s_CreateNeedleFromString(const std::wstring_view wstr);
static std::vector<std::vector<wchar_t>> s_CreateNeedleFromString(const std::wstring_view wstr);
bool _reachedEnd = false;
til::point _coordNext;
@@ -75,10 +76,10 @@ private:
til::point _coordSelEnd;
const til::point _coordAnchor;
const std::vector<std::wstring> _needle;
const std::vector<std::vector<wchar_t>> _needle;
const Direction _direction;
const Sensitivity _sensitivity;
Microsoft::Console::Render::IRenderData& _renderData;
Microsoft::Console::Types::IUiaData& _uiaData;
#ifdef UNIT_TESTING
friend class SearchTests;

View File

@@ -6,11 +6,11 @@
#include "textBuffer.hpp"
#include <til/hash.h>
#include <til/unicode.h>
#include "../renderer/base/renderer.hpp"
#include "../types/inc/utils.hpp"
#include "../types/inc/convert.hpp"
#include "../../types/inc/Utf16Parser.hpp"
#include "../../types/inc/GlyphWidth.hpp"
namespace
@@ -98,7 +98,7 @@ using PointTree = interval_tree::IntervalTree<til::point, size_t>;
// Return Value:
// - constructed object
// Note: may throw exception
TextBuffer::TextBuffer(til::size screenBufferSize,
TextBuffer::TextBuffer(const til::size screenBufferSize,
const TextAttribute defaultAttributes,
const UINT cursorSize,
const bool isActiveBuffer,
@@ -108,14 +108,10 @@ TextBuffer::TextBuffer(til::size screenBufferSize,
_cursor{ cursorSize, *this },
_isActiveBuffer{ isActiveBuffer }
{
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
screenBufferSize.width = std::max(screenBufferSize.width, 1);
screenBufferSize.height = std::max(screenBufferSize.height, 1);
BufferAllocator allocator{ screenBufferSize };
_storage.reserve(allocator.height());
for (til::CoordType i = 0; i < screenBufferSize.height; ++i, ++allocator)
for (til::CoordType i = 0; i < screenBufferSize.Y; ++i, ++allocator)
{
_storage.emplace_back(allocator.chars(), allocator.indices(), allocator.width(), _currentAttributes);
}
@@ -219,10 +215,10 @@ TextBufferTextIterator TextBuffer::GetTextLineDataAt(const til::point at) const
TextBufferCellIterator TextBuffer::GetCellLineDataAt(const til::point at) const
{
til::inclusive_rect limit;
limit.top = at.y;
limit.bottom = at.y;
limit.left = 0;
limit.right = GetSize().RightInclusive();
limit.Top = at.Y;
limit.Bottom = at.Y;
limit.Left = 0;
limit.Right = GetSize().RightInclusive();
return TextBufferCellIterator(*this, at, Viewport::FromInclusive(limit));
}
@@ -266,11 +262,11 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
{
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
const auto coordPrevPosition = _GetPreviousFromCursor();
auto& prevRow = GetRowByOffset(coordPrevPosition.y);
auto& prevRow = GetRowByOffset(coordPrevPosition.Y);
DbcsAttribute prevDbcsAttr = DbcsAttribute::Single;
try
{
prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.x);
prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.X);
}
catch (...)
{
@@ -323,7 +319,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// Erase previous character into an N type.
try
{
prevRow.ClearCell(coordPrevPosition.x);
prevRow.ClearCell(coordPrevPosition.X);
}
catch (...)
{
@@ -360,13 +356,13 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
if (dbcsAttribute == DbcsAttribute::Leading)
{
const auto cursorPosition = GetCursor().GetPosition();
const auto lineWidth = GetLineWidth(cursorPosition.y);
const auto lineWidth = GetLineWidth(cursorPosition.Y);
// If we're about to lead on the last column in the row, we need to add a padding space
if (cursorPosition.x == lineWidth - 1)
if (cursorPosition.X == lineWidth - 1)
{
// set that we're wrapping for double byte reasons
auto& row = GetRowByOffset(cursorPosition.y);
auto& row = GetRowByOffset(cursorPosition.Y);
row.SetDoubleBytePadded(true);
// then move the cursor forward and onto the next row
@@ -421,8 +417,8 @@ OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt,
it = WriteLine(it, lineTarget, wrap);
// Move to the next line down.
lineTarget.x = 0;
++lineTarget.y;
lineTarget.X = 0;
++lineTarget.Y;
}
return it;
@@ -449,8 +445,8 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
}
// Get the row and write the cells
auto& row = GetRowByOffset(target.y);
const auto newIt = row.WriteCells(givenIt, target.x, wrap, limitRight);
auto& row = GetRowByOffset(target.Y);
const auto newIt = row.WriteCells(givenIt, target.X, wrap, limitRight);
// Take the cell distance written and notify that it needs to be repainted.
const auto written = newIt.GetCellDistance(givenIt);
@@ -479,8 +475,8 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
if (fSuccess)
{
// Get the current cursor position
const auto iRow = GetCursor().GetPosition().y; // row stored as logical position, not array position
const auto iCol = GetCursor().GetPosition().x; // column logical and array positions are equal.
const auto iRow = GetCursor().GetPosition().Y; // row stored as logical position, not array position
const auto iCol = GetCursor().GetPosition().X; // column logical and array positions are equal.
// Get the row associated with the given logical position
auto& Row = GetRowByOffset(iRow);
@@ -554,7 +550,7 @@ void TextBuffer::_SetWrapOnCurrentRow() noexcept
void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet) noexcept
{
// The vertical position of the cursor represents the current row we're manipulating.
const auto uiCurrentRowOffset = GetCursor().GetPosition().y;
const auto uiCurrentRowOffset = GetCursor().GetPosition().Y;
// Set the wrap status as appropriate
GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet);
@@ -573,14 +569,14 @@ bool TextBuffer::IncrementCursor()
// Cursor position is stored as logical array indices (starts at 0) for the window
// Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
// So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
const auto iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().y) - 1;
const auto iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().Y) - 1;
// Move the cursor one position to the right
GetCursor().IncrementXPosition(1);
auto fSuccess = true;
// If we've passed the final valid column...
if (GetCursor().GetPosition().x > iFinalColumnIndex)
if (GetCursor().GetPosition().X > iFinalColumnIndex)
{
// Then mark that we've been forced to wrap
_SetWrapOnCurrentRow();
@@ -607,7 +603,7 @@ bool TextBuffer::NewlineCursor()
GetCursor().IncrementYPosition(1);
// If we've passed the final valid row...
if (GetCursor().GetPosition().y > iFinalRowIndex)
if (GetCursor().GetPosition().Y > iFinalRowIndex)
{
// Stay on the final logical/offset row of the buffer.
GetCursor().SetYPosition(iFinalRowIndex);
@@ -681,28 +677,28 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::C
til::point coordEndOfText;
// Search the given viewport by starting at the bottom.
coordEndOfText.y = viewport.BottomInclusive();
coordEndOfText.Y = viewport.BottomInclusive();
const auto& currRow = GetRowByOffset(coordEndOfText.y);
const auto& currRow = GetRowByOffset(coordEndOfText.Y);
// The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character)
coordEndOfText.x = currRow.MeasureRight() - 1;
coordEndOfText.X = currRow.MeasureRight() - 1;
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
const auto viewportTop = viewport.Top();
auto fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); // this row is empty, and we're not at the top
auto fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop); // this row is empty, and we're not at the top
while (fDoBackUp)
{
coordEndOfText.y--;
const auto& backupRow = GetRowByOffset(coordEndOfText.y);
coordEndOfText.Y--;
const auto& backupRow = GetRowByOffset(coordEndOfText.Y);
// We need to back up to the previous row if this line is empty, AND there are more rows
coordEndOfText.x = backupRow.MeasureRight() - 1;
fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop);
coordEndOfText.X = backupRow.MeasureRight() - 1;
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
}
// don't allow negative results
coordEndOfText.y = std::max(coordEndOfText.y, 0);
coordEndOfText.x = std::max(coordEndOfText.x, 0);
coordEndOfText.Y = std::max(coordEndOfText.Y, 0);
coordEndOfText.X = std::max(coordEndOfText.X, 0);
return coordEndOfText;
}
@@ -719,20 +715,20 @@ til::point TextBuffer::_GetPreviousFromCursor() const noexcept
auto coordPosition = GetCursor().GetPosition();
// If we're not at the left edge, simply move the cursor to the left by one
if (coordPosition.x > 0)
if (coordPosition.X > 0)
{
coordPosition.x--;
coordPosition.X--;
}
else
{
// Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
if (coordPosition.y > 0)
if (coordPosition.Y > 0)
{
// move the cursor up one line
coordPosition.y--;
coordPosition.Y--;
// and to the right edge
coordPosition.x = GetLineWidth(coordPosition.y) - 1;
coordPosition.X = GetLineWidth(coordPosition.Y) - 1;
}
}
@@ -879,7 +875,7 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no
void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
{
const auto cursorPosition = GetCursor().GetPosition();
const auto rowIndex = cursorPosition.y;
const auto rowIndex = cursorPosition.Y;
auto& row = GetRowByOffset(rowIndex);
if (row.GetLineRendition() != lineRendition)
{
@@ -930,22 +926,22 @@ til::CoordType TextBuffer::GetLineWidth(const til::CoordType row) const noexcept
til::point TextBuffer::ClampPositionWithinLine(const til::point position) const noexcept
{
const auto rightmostColumn = GetLineWidth(position.y) - 1;
return { std::min(position.x, rightmostColumn), position.y };
const auto rightmostColumn = GetLineWidth(position.Y) - 1;
return { std::min(position.X, rightmostColumn), position.Y };
}
til::point TextBuffer::ScreenToBufferPosition(const til::point position) const noexcept
{
// Use shift right to quickly divide the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
return { position.x >> scale, position.y };
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X >> scale, position.Y };
}
til::point TextBuffer::BufferToScreenPosition(const til::point position) const noexcept
{
// Use shift left to quickly multiply the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
return { position.x << scale, position.y };
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X << scale, position.Y };
}
// Routine Description:
@@ -967,12 +963,8 @@ void TextBuffer::Reset()
// - newSize - new size of screen.
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(til::size newSize) noexcept
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const til::size newSize) noexcept
{
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
newSize.width = std::max(newSize.width, 1);
newSize.height = std::max(newSize.height, 1);
try
{
BufferAllocator allocator{ newSize };
@@ -981,11 +973,11 @@ void TextBuffer::Reset()
const auto attributes = GetCurrentAttributes();
til::CoordType TopRow = 0; // new top row of the screen buffer
if (newSize.height <= GetCursor().GetPosition().y)
if (newSize.Y <= GetCursor().GetPosition().Y)
{
TopRow = GetCursor().GetPosition().y - newSize.height + 1;
TopRow = GetCursor().GetPosition().Y - newSize.Y + 1;
}
const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.height;
const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y;
// rotate rows until the top row is at index 0
std::rotate(_storage.begin(), _storage.begin() + TopRowIndex, _storage.end());
@@ -1096,7 +1088,7 @@ ROW& TextBuffer::_GetFirstRow() noexcept
// - the delimiter class for the given char
DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept
{
return GetRowByOffset(pos.y).DelimiterClassAt(pos.x, wordDelimiters);
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
}
// Method Description:
@@ -1213,7 +1205,7 @@ til::point TextBuffer::_GetWordStartForSelection(const til::point target, const
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
// expand left until we hit the left boundary or a different delimiter class
while (result.x > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
while (result.X > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
{
bufferSize.DecrementInBounds(result);
}
@@ -1331,7 +1323,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
const auto bufferSize = GetSize();
// can't expand right
if (target.x == bufferSize.RightInclusive())
if (target.X == bufferSize.RightInclusive())
{
return target;
}
@@ -1340,7 +1332,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
// expand right until we hit the right boundary or a different delimiter class
while (result.x < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
{
bufferSize.IncrementInBounds(result);
}
@@ -1614,25 +1606,25 @@ const std::vector<til::inclusive_rect> TextBuffer::GetTextRects(til::point start
std::make_tuple(start, end) :
std::make_tuple(end, start);
const auto textRectSize = 1 + lowerCoord.y - higherCoord.y;
const auto textRectSize = 1 + lowerCoord.Y - higherCoord.Y;
textRects.reserve(textRectSize);
for (auto row = higherCoord.y; row <= lowerCoord.y; row++)
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
{
til::inclusive_rect textRow;
textRow.top = row;
textRow.bottom = row;
textRow.Top = row;
textRow.Bottom = row;
if (blockSelection || higherCoord.y == lowerCoord.y)
if (blockSelection || higherCoord.Y == lowerCoord.Y)
{
// set the left and right margin to the left-/right-most respectively
textRow.left = std::min(higherCoord.x, lowerCoord.x);
textRow.right = std::max(higherCoord.x, lowerCoord.x);
textRow.Left = std::min(higherCoord.X, lowerCoord.X);
textRow.Right = std::max(higherCoord.X, lowerCoord.X);
}
else
{
textRow.left = (row == higherCoord.y) ? higherCoord.x : bufferSize.Left();
textRow.right = (row == lowerCoord.y) ? lowerCoord.x : bufferSize.RightInclusive();
textRow.Left = (row == higherCoord.Y) ? higherCoord.X : bufferSize.Left();
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
}
// If we were passed screen coordinates, convert the given range into
@@ -1673,8 +1665,8 @@ std::vector<til::point_span> TextBuffer::GetTextSpans(til::point start, til::poi
for (auto rect : rects)
{
const til::point first = { rect.left, rect.top };
const til::point second = { rect.right, rect.bottom };
const til::point first = { rect.Left, rect.Top };
const til::point second = { rect.Right, rect.Bottom };
textSpans.emplace_back(first, second);
}
}
@@ -1695,16 +1687,16 @@ std::vector<til::point_span> TextBuffer::GetTextSpans(til::point start, til::poi
// equivalent buffer offsets, taking line rendition into account.
if (!bufferCoordinates)
{
higherCoord = ScreenToBufferLine(higherCoord, GetLineRendition(higherCoord.y));
lowerCoord = ScreenToBufferLine(lowerCoord, GetLineRendition(lowerCoord.y));
higherCoord = ScreenToBufferLine(higherCoord, GetLineRendition(higherCoord.Y));
lowerCoord = ScreenToBufferLine(lowerCoord, GetLineRendition(lowerCoord.Y));
}
til::inclusive_rect asRect = { higherCoord.x, higherCoord.y, lowerCoord.x, lowerCoord.y };
til::inclusive_rect asRect = { higherCoord.X, higherCoord.Y, lowerCoord.X, lowerCoord.Y };
_ExpandTextRow(asRect);
higherCoord.x = asRect.left;
higherCoord.y = asRect.top;
lowerCoord.x = asRect.right;
lowerCoord.y = asRect.bottom;
higherCoord.X = asRect.Left;
higherCoord.Y = asRect.Top;
lowerCoord.X = asRect.Right;
lowerCoord.Y = asRect.Bottom;
textSpans.emplace_back(higherCoord, lowerCoord);
}
@@ -1724,10 +1716,10 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
const auto bufferSize = GetSize();
// expand left side of rect
til::point targetPoint{ textRow.left, textRow.top };
til::point targetPoint{ textRow.Left, textRow.Top };
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing)
{
if (targetPoint.x == bufferSize.Left())
if (targetPoint.X == bufferSize.Left())
{
bufferSize.IncrementInBounds(targetPoint);
}
@@ -1735,14 +1727,14 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
{
bufferSize.DecrementInBounds(targetPoint);
}
textRow.left = targetPoint.x;
textRow.Left = targetPoint.X;
}
// expand right side of rect
targetPoint = { textRow.right, textRow.bottom };
targetPoint = { textRow.Right, textRow.Bottom };
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Leading)
{
if (targetPoint.x == bufferSize.RightInclusive())
if (targetPoint.X == bufferSize.RightInclusive())
{
bufferSize.DecrementInBounds(targetPoint);
}
@@ -1750,7 +1742,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
{
bufferSize.IncrementInBounds(targetPoint);
}
textRow.right = targetPoint.x;
textRow.Right = targetPoint.X;
}
}
@@ -1785,7 +1777,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
// for each row in the selection
for (size_t i = 0; i < rows; i++)
{
const auto iRow = selectionRects.at(i).top;
const auto iRow = selectionRects.at(i).Top;
const auto highlight = Viewport::FromInclusive(selectionRects.at(i));
@@ -2337,7 +2329,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const auto cOldCursorPos = oldCursor.GetPosition();
const auto cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
const auto cOldRowsTotal = cOldLastChar.y + 1;
const auto cOldRowsTotal = cOldLastChar.Y + 1;
til::point cNewCursorPos;
auto fFoundCursorPos = false;
@@ -2356,9 +2348,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
const auto newBufferPos = newCursor.GetPosition();
if (newBufferPos.x == 0)
if (newBufferPos.X == 0)
{
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.y);
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
newRow.SetLineRendition(row.GetLineRendition());
}
@@ -2393,7 +2385,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const auto copyRight = iRight;
for (; iOldCol < copyRight; iOldCol++)
{
if (iOldCol == cOldCursorPos.x && iOldRow == cOldCursorPos.y)
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
@@ -2435,9 +2427,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// line in the new buffer, then we didn't wrap. That's fine. just
// copy attributes from the old row till the end of the new row, and
// move on.
const auto newRowY = newCursor.GetPosition().y;
const auto newRowY = newCursor.GetPosition().Y;
auto& newRow = newBuffer.GetRowByOffset(newRowY);
auto newAttrColumn = newCursor.GetPosition().x;
auto newAttrColumn = newCursor.GetPosition().X;
const auto newWidth = newBuffer.GetLineWidth(newRowY);
// Stop when we get to the end of the buffer width, or the new position
// for inserting an attr would be past the right of the new buffer.
@@ -2466,7 +2458,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
if (iOldRow >= positionInfo.value().get().mutableViewportTop)
{
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().y;
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().Y;
foundOldMutable = true;
}
}
@@ -2475,7 +2467,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
if (iOldRow >= positionInfo.value().get().visibleViewportTop)
{
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().y;
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().Y;
foundOldVisible = true;
}
}
@@ -2490,7 +2482,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// only because we ran out of space.
if (iRight < cOldColsTotal && !row.WasWrapForced())
{
if (!fFoundCursorPos && (iRight == cOldCursorPos.x && iOldRow == cOldCursorPos.y))
if (!fFoundCursorPos && (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y))
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
@@ -2530,9 +2522,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// | |
// ^ and the cursor is now here.
const auto coordNewCursor = newCursor.GetPosition();
if (coordNewCursor.x == 0 && coordNewCursor.y > 0)
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
{
if (newBuffer.GetRowByOffset(coordNewCursor.y - 1).WasWrapForced())
if (newBuffer.GetRowByOffset(coordNewCursor.Y - 1).WasWrapForced())
{
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
}
@@ -2546,7 +2538,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// printable character. This is to fix the `color 2f` scenario, where you
// change the buffer colors then resize and everything below the last
// printable char gets reset. See GH #12567
auto newRowY = newCursor.GetPosition().y + 1;
auto newRowY = newCursor.GetPosition().Y + 1;
const auto newHeight = newBuffer.GetSize().Height();
const auto oldHeight = oldBuffer.GetSize().Height();
for (;
@@ -2586,13 +2578,13 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Advance the cursor to the same offset as before
// get the number of newlines and spaces between the old end of text and the old cursor,
// then advance that many newlines and chars
auto iNewlines = cOldCursorPos.y - cOldLastChar.y;
const auto iIncrements = cOldCursorPos.x - cOldLastChar.x;
auto iNewlines = cOldCursorPos.Y - cOldLastChar.Y;
const auto iIncrements = cOldCursorPos.X - cOldLastChar.X;
const auto cNewLastChar = newBuffer.GetLastNonSpaceCharacter();
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
// because the cursor is already on the next line
if (newBuffer.GetRowByOffset(cNewLastChar.y).WasWrapForced())
if (newBuffer.GetRowByOffset(cNewLastChar.Y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
@@ -2600,7 +2592,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
// old buffer will be one more than in this buffer, so new need one LESS.
if (oldBuffer.GetRowByOffset(cOldLastChar.y).WasWrapForced())
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
@@ -2818,14 +2810,16 @@ PointTree TextBuffer::GetPatterns(const til::CoordType firstRow, const til::Coor
// match and the previous match, so we use the size of the prefix
// along with the size of the match to determine the locations
til::CoordType prefixSize = 0;
for (const auto str = i->prefix().str(); const auto& glyph : til::utf16_iterator{ str })
for (const auto parsedGlyph : Utf16Parser::Parse(i->prefix().str()))
{
const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() };
prefixSize += IsGlyphFullWidth(glyph) ? 2 : 1;
}
const auto start = lenUpToThis + prefixSize;
til::CoordType matchSize = 0;
for (const auto str = i->str(); const auto& glyph : til::utf16_iterator{ str })
for (const auto parsedGlyph : Utf16Parser::Parse(i->str()))
{
const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() };
matchSize += IsGlyphFullWidth(glyph) ? 2 : 1;
}
const auto end = start + matchSize;

View File

@@ -44,7 +44,7 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, til::po
// Throw if the coordinate is not limited to the inside of the given buffer.
THROW_HR_IF(E_INVALIDARG, !limits.IsInBounds(pos));
_attrIter += pos.x;
_attrIter += pos.X;
_GenerateView();
}
@@ -115,15 +115,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
// _SetPos() necessitates calling _GenerateView() and thus the construction
// of a new OutputCellView(). This has a high performance impact (ICache spill?).
// The code below inlines _bounds.IncrementInBounds as well as SetPos.
// In the hot path (_pos.y doesn't change) we modify the _view directly.
// In the hot path (_pos.Y doesn't change) we modify the _view directly.
// Hoist these integers which will be used frequently later.
const auto boundsRightInclusive = _bounds.RightInclusive();
const auto boundsLeft = _bounds.Left();
const auto boundsBottomInclusive = _bounds.BottomInclusive();
const auto boundsTop = _bounds.Top();
const auto oldX = _pos.x;
const auto oldY = _pos.y;
const auto oldX = _pos.X;
const auto oldY = _pos.Y;
// Under MSVC writing the individual members of a til::point generates worse assembly
// compared to having them be local variables. This causes a performance impact.
@@ -166,15 +166,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
_view.UpdateText(_pRow->GlyphAt(newX));
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
_pos.x = newX;
_pos.X = newX;
}
else
{
// cold path (_GenerateView is slow)
_pRow = s_GetRow(_buffer, { newX, newY });
_attrIter = _pRow->AttrBegin() + newX;
_pos.x = newX;
_pos.y = newY;
_pos.X = newX;
_pos.Y = newY;
_GenerateView();
}
@@ -289,16 +289,16 @@ ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
// - newPos - The new coordinate position.
void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
{
if (newPos.y != _pos.y)
if (newPos.Y != _pos.Y)
{
_pRow = s_GetRow(_buffer, newPos);
_attrIter = _pRow->AttrBegin();
_pos.x = 0;
_pos.X = 0;
}
if (newPos.x != _pos.x)
if (newPos.X != _pos.X)
{
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.x) - gsl::narrow_cast<ptrdiff_t>(_pos.x);
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.X) - gsl::narrow_cast<ptrdiff_t>(_pos.X);
_attrIter += diff;
}
@@ -317,15 +317,15 @@ void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
// - Pointer to the underlying CharRow structure
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept
{
return &buffer.GetRowByOffset(pos.y);
return &buffer.GetRowByOffset(pos.Y);
}
// Routine Description:
// - Updates the internal view. Call after updating row, attribute, or positions.
void TextBufferCellIterator::_GenerateView() noexcept
{
_view = OutputCellView(_pRow->GlyphAt(_pos.x),
_pRow->DbcsAttrAt(_pos.x),
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
_pRow->DbcsAttrAt(_pos.X),
*_attrIter,
TextAttributeBehavior::Stored);
}

View File

@@ -7,6 +7,7 @@
#include "../textBuffer.hpp"
#include "../../renderer/inc/DummyRenderer.hpp"
#include "../../types/inc/Utf16Parser.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include <IDataSource.h>
@@ -830,7 +831,7 @@ class ReflowTests
for (size_t bufferIndex{ 1 }; bufferIndex < testCase.buffers.size(); ++bufferIndex)
{
const auto& testBuffer{ til::at(testCase.buffers, bufferIndex) };
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.width, testBuffer.size.height));
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.X, testBuffer.size.Y));
auto newBuffer{ _textBufferByReflowingTextBuffer(*textBuffer, testBuffer.size) };

View File

@@ -9,10 +9,7 @@
#include <wil/stl.h>
#include <wil/resource.h>
#include <wil/win32_helpers.h>
#include <gsl/gsl_util>
#include <gsl/pointers>
#include <shellapi.h>
#include <appmodel.h>
// BODGY
//
@@ -28,68 +25,18 @@
// process can successfully elevate.
#pragma warning(suppress : 26461) // we can't change the signature of wWinMain
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int)
{
// This will invoke an elevated terminal in two possible ways. See GH#14501
// In both scenarios, it passes the entire cmdline as-is to the new process.
//
// #1 discover and invoke the app using the GetCurrentApplicationUserModelId
// api using shell:AppsFolder\package!appid
// cmd: shell:AppsFolder\WindowsTerminalDev_8wekyb3d8bbwe!App
// params: new-tab -p {guid}
//
// #2 find and execute WindowsTerminal.exe
// cmd: {same path as this binary}\WindowsTerminal.exe
// params: new-tab -p {guid}
// All of the args passed to us (something like `new-tab -p {guid}`) are in
// pCmdLine
// see if we're a store app we can invoke with shell:AppsFolder
std::wstring appUserModelId;
const auto result = wil::AdaptFixedSizeToAllocatedResult<std::wstring, APPLICATION_USER_MODEL_ID_MAX_LENGTH>(
appUserModelId, [&](PWSTR value, size_t valueLength, gsl::not_null<size_t*> valueLengthNeededWithNull) noexcept -> HRESULT {
UINT32 length = gsl::narrow_cast<UINT32>(valueLength);
const LONG rc = GetCurrentApplicationUserModelId(&length, value);
switch (rc)
{
case ERROR_SUCCESS:
*valueLengthNeededWithNull = length;
return S_OK;
case ERROR_INSUFFICIENT_BUFFER:
*valueLengthNeededWithNull = length;
return S_FALSE; // trigger allocation loop
case APPMODEL_ERROR_NO_APPLICATION:
return E_FAIL; // we are not running as a store app
default:
return E_UNEXPECTED;
}
});
LOG_IF_FAILED(result);
std::wstring cmd = {};
if (result == S_OK && appUserModelId.length() > 0)
{
// scenario #1
cmd = L"shell:AppsFolder\\" + appUserModelId;
}
else
{
// scenario #2
// Get the path to WindowsTerminal.exe, which should live next to us.
std::filesystem::path module{
wil::GetModuleFileNameW<std::wstring>(nullptr)
};
// Swap elevate-shim.exe for WindowsTerminal.exe
module.replace_filename(L"WindowsTerminal.exe");
cmd = module;
}
// Get the path to WindowsTerminal.exe, which should live next to us.
std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
// Swap elevate-shim.exe for WindowsTerminal.exe
module.replace_filename(L"WindowsTerminal.exe");
// Go!
// The cmdline argument passed to WinMain is stripping the first argument.
// Using GetCommandLine() instead for lParameters
// disable warnings from SHELLEXECUTEINFOW struct. We can't fix that.
#pragma warning(push)
#pragma warning(disable : 26476) // Macro uses naked union over variant.
@@ -99,10 +46,8 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
seInfo.cbSize = sizeof(seInfo);
seInfo.fMask = SEE_MASK_DEFAULT;
seInfo.lpVerb = L"runas"; // This asks the shell to elevate the process
seInfo.lpFile = cmd.c_str(); // This is `shell:AppsFolder\...` or `...\WindowsTerminal.exe`
seInfo.lpParameters = GetCommandLine(); // This is `new-tab -p {guid}`
seInfo.lpFile = module.c_str(); // This is `...\WindowsTerminal.exe`
seInfo.lpParameters = pCmdLine; // This is `new-tab -p {guid}`
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
return 0;
}

View File

@@ -217,30 +217,6 @@ namespace SettingsModelLocalTests
{
"name": "Different reference",
"colorScheme": "One Half Dark"
},
{
"name": "rename neither",
"colorScheme":
{
"dark": "One Half Dark",
"light": "One Half Light"
}
},
{
"name": "rename only light",
"colorScheme":
{
"dark": "One Half Dark",
"light": "Campbell"
}
},
{
"name": "rename only dark",
"colorScheme":
{
"dark": "Campbell",
"light": "One Half Light"
}
}
]
},
@@ -313,28 +289,6 @@ namespace SettingsModelLocalTests
"selectionBackground": "#FFFFFF",
"white": "#DCDFE4",
"yellow": "#E5C07B"
},
{
"name": "One Half Light",
"foreground": "#383A42",
"background": "#FAFAFA",
"cursorColor": "#4F525D",
"black": "#383A42",
"red": "#E45649",
"green": "#50A14F",
"yellow": "#C18301",
"blue": "#0184BC",
"purple": "#A626A4",
"cyan": "#0997B3",
"white": "#FAFAFA",
"brightBlack": "#4F525D",
"brightRed": "#DF6C75",
"brightGreen": "#98C379",
"brightYellow": "#E4C07A",
"brightBlue": "#61AFEF",
"brightPurple": "#C577DD",
"brightCyan": "#56B5C1",
"brightWhite": "#FFFFFF"
}
]
})json" };
@@ -342,63 +296,31 @@ namespace SettingsModelLocalTests
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString) };
const auto newName{ L"Campbell (renamed)" };
settings->UpdateColorSchemeReferences(L"Campbell", newName);
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasLightColorSchemeName());
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasColorSchemeName());
const auto& profiles{ settings->AllProfiles() };
{
const auto& prof{ profiles.GetAt(0) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(1) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(2) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasLightColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(3) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(4) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(5) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(6) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
}
}
}

View File

@@ -744,8 +744,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size());
for (const auto& profile : settings->AllProfiles())
{
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().LightColorSchemeName());
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().ColorSchemeName());
}
}
@@ -960,10 +959,6 @@ namespace SettingsModelLocalTests
},
{
"name": "profile3",
"closeOnExit": "automatic"
},
{
"name": "profile4",
"closeOnExit": null
}
]
@@ -973,10 +968,9 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(0).CloseOnExit());
VERIFY_ARE_EQUAL(CloseOnExitMode::Always, settings->AllProfiles().GetAt(1).CloseOnExit());
VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings->AllProfiles().GetAt(2).CloseOnExit());
VERIFY_ARE_EQUAL(CloseOnExitMode::Automatic, settings->AllProfiles().GetAt(3).CloseOnExit());
// Unknown modes parse as "Automatic"
VERIFY_ARE_EQUAL(CloseOnExitMode::Automatic, settings->AllProfiles().GetAt(4).CloseOnExit());
// Unknown modes parse as "Graceful"
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(3).CloseOnExit());
}
void DeserializationTests::TestCloseOnExitCompatibilityShim()
@@ -1530,7 +1524,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size());
// Because the "parent" command didn't have a name, it couldn't be
// placed into the list of commands. It and its children are just
// placed into the list of commands. It and it's children are just
// ignored.
VERIFY_ARE_EQUAL(0u, settings->ActionMap().NameMap().Size());
}

View File

@@ -1,107 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/NewTabMenuEntry.h"
#include "../TerminalSettingsModel/FolderEntry.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../types/inc/colorTable.hpp"
#include "JsonTestClass.h"
#include <defaults.h>
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class NewTabMenuTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(NewTabMenuTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(DefaultsToRemainingProfiles);
TEST_METHOD(ParseEmptyFolder);
};
void NewTabMenuTests::DefaultsToRemainingProfiles()
{
Log::Comment(L"If the user doesn't customize the menu, put one entry for each profile");
static constexpr std::string_view settingsString{ R"json({
})json" };
try
{
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
const auto& entries = settings->GlobalSettings().NewTabMenu();
VERIFY_ARE_EQUAL(1u, entries.Size());
VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType::RemainingProfiles, entries.GetAt(0).Type());
}
catch (const SettingsException& ex)
{
auto loadError = ex.Error();
loadError;
throw ex;
}
catch (const SettingsTypedDeserializationException& e)
{
auto deserializationErrorMessage = til::u8u16(e.what());
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
throw e;
}
}
void NewTabMenuTests::ParseEmptyFolder()
{
Log::Comment(L"GH #14557 - An empty folder entry shouldn't crash");
static constexpr std::string_view settingsString{ R"json({
"newTabMenu": [
{ "type": "folder" }
]
})json" };
try
{
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
const auto& entries = settings->GlobalSettings().NewTabMenu();
VERIFY_ARE_EQUAL(1u, entries.Size());
}
catch (const SettingsException& ex)
{
auto loadError = ex.Error();
loadError;
throw ex;
}
catch (const SettingsTypedDeserializationException& e)
{
auto deserializationErrorMessage = til::u8u16(e.what());
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
throw e;
}
}
}

View File

@@ -134,7 +134,7 @@ namespace SettingsModelLocalTests
"font": {
"face": "Cascadia Mono",
"size": 12.0,
"size": 12,
"weight": "normal"
},
"padding": "8, 8, 8, 8",
@@ -250,8 +250,8 @@ namespace SettingsModelLocalTests
// complex command with key chords
static constexpr std::string_view actionsString4A{ R"([
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+c" },
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
])" };
// GH#13323 - these can be fragile. In the past, the order these get
// re-serialized as has been not entirely stable. We don't really care
@@ -260,7 +260,7 @@ namespace SettingsModelLocalTests
// itself. Feel free to change as needed.
static constexpr std::string_view actionsString4B{ R"([
{ "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" },
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
])" };
// command with name and icon and multiple key chords
@@ -284,8 +284,8 @@ namespace SettingsModelLocalTests
{
"name": "Change font size...",
"commands": [
{ "command": { "action": "adjustFontSize", "delta": 1.0 } },
{ "command": { "action": "adjustFontSize", "delta": -1.0 } },
{ "command": { "action": "adjustFontSize", "delta": 1 } },
{ "command": { "action": "adjustFontSize", "delta": -1 } },
{ "command": "resetFontSize" },
]
}
@@ -406,12 +406,6 @@ namespace SettingsModelLocalTests
"$schema" : "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
"newTabMenu":
[
{
"type": "remainingProfiles"
}
],
"profiles": {
"defaults": {
"font": {
@@ -484,7 +478,7 @@ namespace SettingsModelLocalTests
"name": "Profile with legacy font settings",
"fontFace": "Cascadia Mono",
"fontSize": 12.0,
"fontSize": 12,
"fontWeight": "normal"
})" };
@@ -494,7 +488,7 @@ namespace SettingsModelLocalTests
"font": {
"face": "Cascadia Mono",
"size": 12.0,
"size": 12,
"weight": "normal"
}
})" };

View File

@@ -8,7 +8,7 @@
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into its own
TestHostApp will manually copy the output of this project into it's own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
@@ -39,7 +39,6 @@
<ClCompile Include="KeyBindingsTests.cpp" />
<ClCompile Include="CommandTests.cpp" />
<ClCompile Include="DeserializationTests.cpp" />
<ClCompile Include="NewTabMenuTests.cpp" />
<ClCompile Include="SerializationTests.cpp" />
<ClCompile Include="TerminalSettingsTests.cpp" />
<ClCompile Include="ThemeTests.cpp" />

View File

@@ -779,22 +779,21 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(6u, settings->ActiveProfiles().Size());
VERIFY_ARE_EQUAL(2u, settings->GlobalSettings().ColorSchemes().Size());
auto createTerminalSettings = [&](const auto& profile, const auto& schemes, const auto& Theme) {
auto createTerminalSettings = [&](const auto& profile, const auto& schemes) {
auto terminalSettings{ winrt::make_self<implementation::TerminalSettings>() };
terminalSettings->_ApplyProfileSettings(profile);
terminalSettings->_ApplyAppearanceSettings(profile.DefaultAppearance(), schemes, Theme);
terminalSettings->_ApplyAppearanceSettings(profile.DefaultAppearance(), schemes);
return terminalSettings;
};
const auto activeProfiles = settings->ActiveProfiles();
const auto colorSchemes = settings->GlobalSettings().ColorSchemes();
const auto currentTheme = settings->GlobalSettings().CurrentTheme();
const auto terminalSettings0 = createTerminalSettings(activeProfiles.GetAt(0), colorSchemes, currentTheme);
const auto terminalSettings1 = createTerminalSettings(activeProfiles.GetAt(1), colorSchemes, currentTheme);
const auto terminalSettings2 = createTerminalSettings(activeProfiles.GetAt(2), colorSchemes, currentTheme);
const auto terminalSettings3 = createTerminalSettings(activeProfiles.GetAt(3), colorSchemes, currentTheme);
const auto terminalSettings4 = createTerminalSettings(activeProfiles.GetAt(4), colorSchemes, currentTheme);
const auto terminalSettings5 = createTerminalSettings(activeProfiles.GetAt(5), colorSchemes, currentTheme);
const auto terminalSettings0 = createTerminalSettings(activeProfiles.GetAt(0), colorSchemes);
const auto terminalSettings1 = createTerminalSettings(activeProfiles.GetAt(1), colorSchemes);
const auto terminalSettings2 = createTerminalSettings(activeProfiles.GetAt(2), colorSchemes);
const auto terminalSettings3 = createTerminalSettings(activeProfiles.GetAt(3), colorSchemes);
const auto terminalSettings4 = createTerminalSettings(activeProfiles.GetAt(4), colorSchemes);
const auto terminalSettings5 = createTerminalSettings(activeProfiles.GetAt(5), colorSchemes);
VERIFY_ARE_EQUAL(til::color(0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default

View File

@@ -64,8 +64,7 @@ namespace SettingsModelLocalTests
},
"window":
{
"applicationTheme": "light",
"useMica": true
"applicationTheme": "light"
}
})" };
@@ -81,7 +80,6 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(theme->Window());
VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Light, theme->Window().RequestedTheme());
VERIFY_ARE_EQUAL(true, theme->Window().UseMica());
}
void ThemeTests::ParseEmptyTheme()
@@ -163,8 +161,7 @@ namespace SettingsModelLocalTests
},
"window":
{
"applicationTheme": "light",
"useMica": true
"applicationTheme": "light"
}
},
{
@@ -175,16 +172,14 @@ namespace SettingsModelLocalTests
},
"window":
{
"applicationTheme": "light",
"useMica": true
"applicationTheme": "light"
}
},
{
"name": "backgroundOmittedEntirely",
"window":
{
"applicationTheme": "light",
"useMica": true
"applicationTheme": "light"
}
}
]
@@ -239,8 +234,7 @@ namespace SettingsModelLocalTests
"tabRow": {},
"window":
{
"applicationTheme": "light",
"useMica": true
"applicationTheme": "light"
}
}
]

View File

@@ -44,7 +44,6 @@ Author(s):
#include <Windows.Graphics.Imaging.Interop.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
#include <winrt/Windows.UI.ViewManagement.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ui.xaml.media.h>

View File

@@ -8,7 +8,7 @@
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into its own
TestHostApp will manually copy the output of this project into it's own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
@@ -23,6 +23,7 @@
<PropertyGroup Label="NuGet Dependencies">
<!-- TerminalCppWinrt is intentionally not set -->
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />

View File

@@ -24,6 +24,25 @@ static constexpr bool _IsMouseMessage(UINT uMsg)
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL;
}
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
// See microsoft/terminal#2066 for more info.
static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) noexcept
{
return false; // glyph is not wide.
}
static bool _EnsureStaticInitialization()
{
// use C++11 magic statics to make sure we only do this once.
static auto initialized = []() {
// *** THIS IS A SINGLETON ***
SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
return true;
}();
return initialized;
}
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
HWND hwnd,
UINT uMsg,
@@ -102,11 +121,11 @@ try
}
break;
case WM_RBUTTONDOWN:
if (const auto& termCore{ terminal->_terminal }; termCore && termCore->IsSelectionActive())
if (terminal->_terminal->IsSelectionActive())
{
try
{
const auto bufferData = termCore->RetrieveSelectedTextFromBuffer(false);
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
TerminalClearSelection(terminal);
}
@@ -156,7 +175,7 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
return RegisterClassW(&wc) != 0;
}
HwndTerminal::HwndTerminal(HWND parentHwnd) noexcept :
HwndTerminal::HwndTerminal(HWND parentHwnd) :
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, 14, CP_UTF8 },
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
_uiaProvider{ nullptr },
@@ -164,6 +183,8 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) noexcept :
_pfnWriteCallback{ nullptr },
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
{
_EnsureStaticInitialization();
auto hInstance = wil::GetModuleInstanceHandle();
if (RegisterTermClass(hInstance))
@@ -260,10 +281,6 @@ CATCH_LOG();
void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> callback)
{
if (!_terminal)
{
return;
}
_terminal->SetScrollPositionChangedCallback(callback);
}
@@ -287,7 +304,7 @@ void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*))
_pfnWriteCallback = callback;
}
::Microsoft::Console::Render::IRenderData* HwndTerminal::GetRenderData() const noexcept
::Microsoft::Console::Types::IUiaData* HwndTerminal::GetUiaData() const noexcept
{
return _terminal.get();
}
@@ -299,10 +316,6 @@ HWND HwndTerminal::GetHwnd() const noexcept
void HwndTerminal::_UpdateFont(int newDpi)
{
if (!_terminal)
{
return;
}
_currentDpi = newDpi;
auto lock = _terminal->LockForWriting();
@@ -319,12 +332,8 @@ IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
{
try
{
if (!_terminal)
{
return nullptr;
}
auto lock = _terminal->LockForWriting();
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<HwndTerminalAutomationPeer>(&_uiaProvider, this->GetRenderData(), this));
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<HwndTerminalAutomationPeer>(&_uiaProvider, this->GetUiaData(), this));
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(_uiaProvider.Get());
LOG_IF_FAILED(_uiaEngine->Enable());
_renderer->AddRenderEngine(_uiaEngine.get());
@@ -341,7 +350,6 @@ IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimensions)
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
RETURN_HR_IF_NULL(E_INVALIDARG, dimensions);
auto lock = _terminal->LockForWriting();
@@ -357,30 +365,21 @@ HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimen
const auto viewInPixels = Viewport::FromDimensions(windowSize);
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
// Guard against resizing the window to 0 columns/rows, which the text buffer classes don't really support.
auto size = vp.Dimensions();
size.width = std::max(size.width, 1);
size.height = std::max(size.height, 1);
// If this function succeeds with S_FALSE, then the terminal didn't
// actually change size. No need to notify the connection of this
// no-op.
// TODO: MSFT:20642295 Resizing the buffer will corrupt it
// I believe we'll need support for CSI 2J, and additionally I think
// we're resetting the viewport to the top
RETURN_IF_FAILED(_terminal->UserResize(size));
dimensions->width = size.width;
dimensions->height = size.height;
RETURN_IF_FAILED(_terminal->UserResize({ vp.Width(), vp.Height() }));
dimensions->X = vp.Width();
dimensions->Y = vp.Height();
return S_OK;
}
void HwndTerminal::SendOutput(std::wstring_view data)
{
if (!_terminal)
{
return;
}
_terminal->Write(data);
}
@@ -454,8 +453,8 @@ HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ ti
const auto viewInCharacters = Viewport::FromDimensions(dimensionsInCharacters);
const auto viewInPixels = publicTerminal->_renderEngine->GetViewportInPixels(viewInCharacters);
dimensionsInPixels->width = viewInPixels.Width();
dimensionsInPixels->height = viewInPixels.Height();
dimensionsInPixels->cx = viewInPixels.Width();
dimensionsInPixels->cy = viewInPixels.Height();
til::size unused;
@@ -474,11 +473,11 @@ HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ til::CoordTyp
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto viewInPixels = Viewport::FromDimensions({ width, height });
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { width, height });
const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);
dimensions->width = viewInCharacters.Width();
dimensions->height = viewInCharacters.Height();
dimensions->X = viewInCharacters.Width();
dimensions->Y = viewInCharacters.Height();
return S_OK;
}
@@ -491,10 +490,8 @@ void _stdcall TerminalDpiChanged(void* terminal, int newDpi)
void _stdcall TerminalUserScroll(void* terminal, int viewTop)
{
if (const auto publicTerminal = static_cast<const HwndTerminal*>(terminal); publicTerminal && publicTerminal->_terminal)
{
publicTerminal->_terminal->UserScrollViewport(viewTop);
}
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
publicTerminal->_terminal->UserScrollViewport(viewTop);
}
const unsigned int HwndTerminal::_NumberOfClicks(til::point point, std::chrono::steady_clock::time_point timestamp) noexcept
@@ -516,7 +513,6 @@ const unsigned int HwndTerminal::_NumberOfClicks(til::point point, std::chrono::
HRESULT HwndTerminal::_StartSelection(LPARAM lParam) noexcept
try
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
const til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
@@ -560,7 +556,6 @@ CATCH_RETURN();
HRESULT HwndTerminal::_MoveSelection(LPARAM lParam) noexcept
try
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
const til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
@@ -599,10 +594,6 @@ CATCH_RETURN();
void HwndTerminal::_ClearSelection() noexcept
try
{
if (!_terminal)
{
return;
}
auto lock{ _terminal->LockForWriting() };
_terminal->ClearSelection();
_renderer->TriggerSelection();
@@ -617,21 +608,15 @@ void _stdcall TerminalClearSelection(void* terminal)
bool _stdcall TerminalIsSelectionActive(void* terminal)
{
if (const auto publicTerminal = static_cast<const HwndTerminal*>(terminal); publicTerminal && publicTerminal->_terminal)
{
return publicTerminal->_terminal->IsSelectionActive();
}
return false;
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto selectionActive = publicTerminal->_terminal->IsSelectionActive();
return selectionActive;
}
// Returns the selected text in the terminal.
const wchar_t* _stdcall TerminalGetSelection(void* terminal)
{
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return nullptr;
}
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
publicTerminal->_ClearSelection();
@@ -683,17 +668,12 @@ bool HwndTerminal::_CanSendVTMouseInput() const noexcept
{
// Only allow the transit of mouse events if shift isn't pressed.
const auto shiftPressed = GetKeyState(VK_SHIFT) < 0;
return !shiftPressed && _focused && _terminal && _terminal->IsTrackingMouseInput();
return !shiftPressed && _focused && _terminal->IsTrackingMouseInput();
}
bool HwndTerminal::_SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept
try
{
if (!_terminal)
{
return false;
}
til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
@@ -726,11 +706,6 @@ catch (...)
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept
try
{
if (!_terminal)
{
return;
}
auto modifiers = getControlKeyState();
if (WI_IsFlagSet(flags, ENHANCED_KEY))
{
@@ -747,11 +722,7 @@ CATCH_LOG();
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept
try
{
if (!_terminal)
{
return;
}
else if (_terminal->IsSelectionActive())
if (_terminal->IsSelectionActive())
{
_ClearSelection();
if (ch == UNICODE_ESC)
@@ -799,10 +770,6 @@ void _stdcall DestroyTerminal(void* terminal)
void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, til::CoordType fontSize, int newDpi)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return;
}
{
auto lock = publicTerminal->_terminal->LockForWriting();
@@ -838,7 +805,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
void _stdcall TerminalBlinkCursor(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal || (!publicTerminal->_terminal->IsCursorBlinkingAllowed() && publicTerminal->_terminal->IsCursorVisible()))
if (!publicTerminal->_terminal->IsCursorBlinkingAllowed() && publicTerminal->_terminal->IsCursorVisible())
{
return;
}
@@ -849,10 +816,6 @@ void _stdcall TerminalBlinkCursor(void* terminal)
void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return;
}
publicTerminal->_terminal->SetCursorOn(visible);
}
@@ -884,7 +847,6 @@ void __stdcall TerminalKillFocus(void* terminal)
HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting)
try
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
std::wstring finalString;
// Concatenate strings into one giant string to put onto the clipboard.
@@ -922,7 +884,7 @@ try
if (fAlsoCopyFormatting)
{
const auto& fontData = _actualFont;
const int iFontHeightPoints = fontData.GetUnscaledSize().height; // this renderer uses points already
const int iFontHeightPoints = fontData.GetUnscaledSize().Y; // this renderer uses points already
const auto bgColor = _terminal->GetAttributeColors({}).second;
auto HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor);
@@ -1041,11 +1003,7 @@ double HwndTerminal::GetScaleFactor() const noexcept
void HwndTerminal::ChangeViewport(const til::inclusive_rect& NewWindow)
{
if (!_terminal)
{
return;
}
_terminal->UserScrollViewport(NewWindow.top);
_terminal->UserScrollViewport(NewWindow.Top);
}
HRESULT HwndTerminal::GetHostUiaProvider(IRawElementProviderSimple** provider) noexcept

View File

@@ -49,7 +49,7 @@ __declspec(dllexport) void _stdcall TerminalKillFocus(void* terminal);
struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
{
public:
HwndTerminal(HWND hwnd) noexcept;
HwndTerminal(HWND hwnd);
HwndTerminal(const HwndTerminal&) = default;
HwndTerminal(HwndTerminal&&) = default;
@@ -63,7 +63,7 @@ public:
HRESULT Refresh(const til::size windowSize, _Out_ til::size* dimensions);
void RegisterScrollCallback(std::function<void(int, int, int)> callback);
void RegisterWriteCallback(const void _stdcall callback(wchar_t*));
::Microsoft::Console::Render::IRenderData* GetRenderData() const noexcept;
::Microsoft::Console::Types::IUiaData* GetUiaData() const noexcept;
HWND GetHwnd() const noexcept;
static LRESULT CALLBACK HwndTerminalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;

View File

@@ -148,7 +148,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
// Method Description:
// - Tell this window to display its window ID. We'll raise a
// - Tell this window to display it's window ID. We'll raise a
// DisplayWindowIdRequested event, which will get handled in the AppHost,
// and used to tell the app to display the ID toast.
// Arguments:

View File

@@ -12,7 +12,7 @@ Abstract:
ShouldCreateWindow is false, that implies that some other window process was
given the commandline for handling, and the caller should just exit.
- If ShouldCreateWindow is true, the Id property may or may not contain an ID
that the new window should use as its ID.
that the new window should use as it's ID.
--*/

View File

@@ -109,7 +109,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
try
{
// MSFT:38542548 _We believe_ that this is the source of the
// crash here. After we get the result, stash its values into a
// crash here. After we get the result, stash it's values into a
// local copy, so that we can check them later. If the Monarch
// dies between now and the inspection of
// `result.ShouldCreateWindow` below, we don't want to explode
@@ -310,7 +310,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
// This right here will just tell us to stash the args away for the
// future. The AppHost hasn't yet set up the callbacks, and the rest
// future. The AppHost hasnt yet set up the callbacks, and the rest
// of the app hasn't started at all. We'll note them and come back
// later.
_peasant.ExecuteCommandline(args);
@@ -488,7 +488,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
try
{
// Wrap this in its own try/catch, because this can throw.
// Wrap this in it's own try/catch, because this can throw.
_createMonarchAndCallbacks();
}
catch (...)
@@ -572,7 +572,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// monarch.
try
{
// This might fail to even ask the monarch for its PID.
// This might fail to even ask the monarch for it's PID.
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(_monarch.GetPID())) };
@@ -647,7 +647,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
catch (...)
{
// Theoretically, if window[1] dies when we're trying to get
// its PID we'll get here. If we just try to do the election
// it's PID we'll get here. If we just try to do the election
// once here, it's possible we might elect window[2], but have
// it die before we add ourselves as a peasant. That
// _performElection call will throw, and we wouldn't catch it

View File

@@ -49,7 +49,7 @@
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
<!-- Reference Microsoft.Terminal.RemotingLib here, so we can use its winmd as
<!-- Reference Microsoft.Terminal.RemotingLib here, so we can use it's winmd as
our winmd. This didn't work correctly in VS2017, you'd need to
manually reference the lib -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\Remoting\Microsoft.Terminal.RemotingLib.vcxproj">

View File

@@ -50,6 +50,7 @@ namespace winrt::TerminalApp::implementation
{
case ShortcutAction::SetColorScheme:
case ShortcutAction::AdjustOpacity:
case ShortcutAction::SendInput:
{
_RunRestorePreviews();
break;
@@ -150,6 +151,12 @@ namespace winrt::TerminalApp::implementation
case ShortcutAction::AdjustOpacity:
_PreviewAdjustOpacity(args.Args().try_as<AdjustOpacityArgs>());
break;
case ShortcutAction::SendInput:
{
_PreviewSendInput(args.Args().try_as<SendInputArgs>());
break;
}
}
// GH#9818 Other ideas for actions that could be preview-able:
@@ -163,6 +170,27 @@ namespace winrt::TerminalApp::implementation
_lastPreviewedAction = args;
}
void TerminalPage::_PreviewSendInput(const Settings::Model::SendInputArgs& args)
{
const auto backup = _restorePreviewFuncs.empty();
_ApplyToActiveControls([&](const auto& control) {
// // Stash a copy of the original opacity.
// auto originalOpacity{ control.BackgroundOpacity() };
// Apply the new opacity
control.PreviewInput(args.Input());
if (backup)
{
_restorePreviewFuncs.emplace_back([=]() {
// On dismiss:
control.PreviewInput(L"");
});
}
});
}
// Method Description:
// - Handler for the CommandPalette::PreviewAction event. The Command
// Palette will raise this even when an action is selected, but _not_

View File

@@ -12,12 +12,19 @@ using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Navigation;
namespace xaml = ::winrt::Windows::UI::Xaml;
namespace winrt::TerminalApp::implementation
{
App::App()
{
// This is the same trick that Initialize() is about to use to figure out whether we're coming
// from a UWP context or from a Win32 context
// See https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/blob/52611c57d89554f357f281d0c79036426a7d9257/Microsoft.Toolkit.Win32.UI.XamlApplication/XamlApplication.cpp#L42
const auto dispatcherQueue = ::winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (dispatcherQueue)
{
_isUwp = true;
}
Initialize();
// Disable XAML's automatic backplating of text when in High Contrast
@@ -26,50 +33,12 @@ namespace winrt::TerminalApp::implementation
HighContrastAdjustment(::winrt::Windows::UI::Xaml::ApplicationHighContrastAdjustment::None);
}
void App::Initialize()
{
const auto dispatcherQueue = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (!dispatcherQueue)
{
_windowsXamlManager = xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
}
else
{
_isUwp = true;
}
}
AppLogic App::Logic()
{
static AppLogic logic;
return logic;
}
void App::Close()
{
if (_bIsClosed)
{
return;
}
_bIsClosed = true;
if (_windowsXamlManager)
{
_windowsXamlManager.Close();
}
_windowsXamlManager = nullptr;
Exit();
{
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
::DispatchMessageW(&msg);
}
}
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
@@ -85,7 +54,7 @@ namespace winrt::TerminalApp::implementation
{
auto logic = Logic();
logic.RunAsUwp(); // Must set UWP status first, settings might change based on it.
logic.ReloadSettings();
logic.LoadSettings();
logic.Create();
auto page = logic.GetRoot().as<TerminalPage>();

View File

@@ -5,7 +5,6 @@
#include "App.g.h"
#include "App.base.h"
#include <winrt/Windows.UI.Xaml.Hosting.h>
namespace winrt::TerminalApp::implementation
{
@@ -14,22 +13,11 @@ namespace winrt::TerminalApp::implementation
public:
App();
void OnLaunched(const Windows::ApplicationModel::Activation::LaunchActivatedEventArgs&);
void Initialize();
TerminalApp::AppLogic Logic();
void Close();
bool IsDisposed() const
{
return _bIsClosed;
}
private:
bool _isUwp = false;
winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager _windowsXamlManager = nullptr;
winrt::Windows::Foundation::Collections::IVector<winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider> _providers = winrt::single_threaded_vector<Windows::UI::Xaml::Markup::IXamlMetadataProvider>();
bool _bIsClosed = false;
};
}

View File

@@ -7,12 +7,10 @@ namespace TerminalApp
{
// ADD ARBITRARY APP LOGIC TO AppLogic.idl, NOT HERE.
// This is for XAML platform setup only.
[default_interface] runtimeclass App : Windows.UI.Xaml.Application, Windows.Foundation.IClosable
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
{
App();
AppLogic Logic { get; };
Boolean IsDisposed { get; };
}
}

View File

@@ -2,19 +2,20 @@
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Application x:Class="TerminalApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:TerminalApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Toolkit:XamlApplication x:Class="TerminalApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:TerminalApp"
xmlns:Toolkit="using:Microsoft.Toolkit.Win32.UI.XamlHost"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--
If you want to prove this works, then add `RequestedTheme="Light"` to
the properties on the XamlApplication
-->
<Application.Resources>
<Toolkit:XamlApplication.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -138,7 +139,7 @@
evaluated as SolidBackgroundFillColorBase. If we try
to use those resources directly though, we don't get
the properly themed versions. Presumably because the
App itself can't have its RequestedTheme changed at
App itself can't have it's RequestedTheme changed at
runtime.
However, after more discussion with the WinUI
@@ -152,7 +153,7 @@
Color="#2e2e2e" />
<StaticResource x:Key="UnfocusedBorderBrush"
ResourceKey="TabViewBackground" />
ResourceKey="ApplicationPageBackgroundThemeBrush" />
<SolidColorBrush x:Key="SettingsUiTabBrush"
Color="#0c0c0c" />
@@ -168,7 +169,7 @@
Color="#e8e8e8" />
<StaticResource x:Key="UnfocusedBorderBrush"
ResourceKey="TabViewBackground" />
ResourceKey="ApplicationPageBackgroundThemeBrush" />
<SolidColorBrush x:Key="SettingsUiTabBrush"
Color="#ffffff" />
@@ -201,5 +202,5 @@
</ResourceDictionary>
</Application.Resources>
</Application>
</Toolkit:XamlApplication.Resources>
</Toolkit:XamlApplication>

View File

@@ -510,10 +510,7 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleFind(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto activeTab{ _GetFocusedTabImpl() })
{
_Find(*activeTab);
}
_Find();
args.Handled(true);
}
@@ -1185,4 +1182,95 @@ namespace winrt::TerminalApp::implementation
args.Handled(handled);
}
}
void TerminalPage::_HandleToggleTaskView(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<ToggleTaskViewArgs>())
{
auto source = realArgs.Source();
switch (source)
{
case TaskSource::Prompt:
{
auto commandsCollection = _settings.GlobalSettings().ActionMap().FilterToSendInput();
if (const auto& control{ _GetActiveControl() })
{
const auto context = control.DirectoryHistory();
auto cwd = context.CurrentWorkingDirectory();
if (!cwd.empty())
{
// TODO! don't read the file on the UI thread you idiot
auto localTasks = CascadiaSettings::ReadFile(cwd + L"\\.wt.json");
if (!localTasks.empty())
{
Command::AddLocalCommands(commandsCollection, localTasks);
}
}
}
_openTaskView(commandsCollection);
args.Handled(true);
}
break;
case TaskSource::CommandHistory:
{
if (const auto& control{ _GetActiveControl() })
{
const auto context = control.CommandHistory();
_openTaskView(Command::HistoryToCommands(context.History(), context.CurrentCommandline(), false));
}
args.Handled(true);
}
break;
case TaskSource::DirectoryHistory:
{
if (const auto& control{ _GetActiveControl() })
{
const auto context = control.DirectoryHistory();
_openTaskView(Command::HistoryToCommands(context.History(), L"", true));
}
args.Handled(true);
}
break;
case TaskSource::Suggestions:
{
_openSuggestionsPrompt();
args.Handled(true);
}
break;
}
}
}
}
void TerminalPage::_HandleSaveTask(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<SaveTaskArgs>())
{
ActionAndArgs newAction{};
newAction.Action(ShortcutAction::SendInput);
// _getNewTerminalArgs MUST be called before parsing any other options,
// as it might clear those options while finding the commandline
SendInputArgs sendInputArgs{ realArgs.Commandline() };
// sendInputArgs.Input(args.Commandline());
newAction.Args(sendInputArgs);
// ActionMap::RegisterKeyBinding(null, sendInput(...))
_settings.GlobalSettings().ActionMap().RegisterKeyBinding(nullptr, newAction);
args.Handled(true);
}
}
}
}

View File

@@ -97,7 +97,7 @@ int AppCommandlineArgs::ParseCommand(const Commandline& command)
}
// Method Description:
// - Calls App::exit() for the provided command, and collects its output into
// - Calls App::exit() for the provided command, and collects it's output into
// our _exitMessage buffer.
// Arguments:
// - command: Either the root App object, or a subcommand for which to call exit() on.
@@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser()
_buildMovePaneParser();
_buildSwapPaneParser();
_buildFocusPaneParser();
_buildSaveParser();
}
// Method Description:
@@ -537,6 +538,61 @@ void AppCommandlineArgs::_buildFocusPaneParser()
setupSubcommand(_focusPaneShort);
}
void AppCommandlineArgs::_buildSaveParser()
{
_saveCommand = _app.add_subcommand("save", "TODO Desc");
// _newTabShort.subcommand = _app.add_subcommand("nt", RS_A(L"CmdNTDesc"));
auto setupSubcommand = [this](auto* subcommand) {
subcommand->add_option("command", _commandline, RS_A(L"CmdCommandArgDesc"));
subcommand->positionals_at_end(true);
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
// Build the NewTab action from the values we've parsed on the commandline.
ActionAndArgs saveAction{};
saveAction.Action(ShortcutAction::SaveTask);
// _getNewTerminalArgs MUST be called before parsing any other options,
// as it might clear those options while finding the commandline
SaveTaskArgs args{};
if (!_commandline.empty())
{
std::ostringstream cmdlineBuffer;
for (const auto& arg : _commandline)
{
if (cmdlineBuffer.tellp() != 0)
{
// If there's already something in here, prepend a space
cmdlineBuffer << ' ';
}
if (arg.find(" ") != std::string::npos)
{
cmdlineBuffer << '"' << arg << '"';
}
else
{
cmdlineBuffer << arg;
}
}
args.Commandline(winrt::to_hstring(cmdlineBuffer.str()));
}
saveAction.Args(args);
_startupActions.push_back(saveAction);
});
};
setupSubcommand(_saveCommand);
// setupSubcommand(_newTabShort);
}
// Method Description:
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
// that subcommand to support all the properties in a NewTerminalArgs.
@@ -680,7 +736,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
*_focusPaneCommand ||
*_focusPaneShort ||
*_newPaneShort.subcommand ||
*_newPaneCommand.subcommand);
*_newPaneCommand.subcommand ||
*_saveCommand);
}
// Method Description:

View File

@@ -90,6 +90,7 @@ private:
CLI::App* _swapPaneCommand;
CLI::App* _focusPaneCommand;
CLI::App* _focusPaneShort;
CLI::App* _saveCommand;
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
@@ -142,6 +143,7 @@ private:
void _buildMovePaneParser();
void _buildSwapPaneParser();
void _buildFocusPaneParser();
void _buildSaveParser();
bool _noCommandsProvided();
void _resetStateToDefault();
int _handleExit(const CLI::App& command, const CLI::Error& e);

View File

@@ -52,7 +52,6 @@ static const std::array settingsLoadWarningsLabels {
USES_RESOURCE(L"FailedToParseStartupActions"),
USES_RESOURCE(L"FailedToParseSubCommands"),
USES_RESOURCE(L"UnknownTheme"),
USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
};
static const std::array settingsLoadErrorsLabels {
USES_RESOURCE(L"NoProfilesText"),
@@ -192,7 +191,7 @@ namespace winrt::TerminalApp::implementation
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() })
{
self->ReloadSettings();
self->_ReloadSettings();
}
});
@@ -616,7 +615,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
winrt::Windows::Foundation::Size proposedSize{};
@@ -697,7 +696,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
// GH#4620/#5801 - If the user passed --maximized or --fullscreen on the
@@ -731,7 +730,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
auto initialPosition{ _settings.GlobalSettings().InitialPosition() };
@@ -761,7 +760,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
// If the position has been specified on the commandline, don't center on launch
return _settings.GlobalSettings().CenterOnLaunch() && !_appArgs.GetPosition().has_value();
@@ -777,7 +776,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return _settings.GlobalSettings().ShowTabsInTitlebar();
@@ -788,7 +787,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return _settings.GlobalSettings().AlwaysOnTop();
@@ -868,6 +867,36 @@ namespace winrt::TerminalApp::implementation
return hr;
}
// Method Description:
// - Initialized our settings. See CascadiaSettings for more details.
// Additionally hooks up our callbacks for keybinding events to the
// keybindings object.
// NOTE: This must be called from a MTA if we're running as a packaged
// application. The Windows.Storage APIs require a MTA. If this isn't
// happening during startup, it'll need to happen on a background thread.
void AppLogic::LoadSettings()
{
// Attempt to load the settings.
// If it fails,
// - use Default settings,
// - don't persist them (LoadAll won't save them in this case).
// - _settingsLoadedResult will be set to an error, indicating that
// we should display the loading error.
// * We can't display the error now, because we might not have a
// UI yet. We'll display the error in _OnLoaded.
_settingsLoadedResult = _TryLoadSettings();
if (FAILED(_settingsLoadedResult))
{
_settings = CascadiaSettings::LoadDefaults();
}
_loadedInitialSettings = true;
// Register for directory change notification.
_RegisterSettingsChange();
}
// Call this function after loading your _settings.
// It handles any CPU intensive settings updates (like updating the Jumplist)
// which should thus only occur if the settings file actually changed.
@@ -891,7 +920,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Registers for changes to the settings folder and upon a updated settings
// profile calls ReloadSettings().
// profile calls _ReloadSettings().
// Arguments:
// - <none>
// Return Value:
@@ -1026,14 +1055,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Reloads the settings from the settings.json file.
// - When this is called the first time, this initializes our settings. See
// CascadiaSettings for more details. Additionally hooks up our callbacks
// for keybinding events to the keybindings object.
// - NOTE: when called initially, this must be called from a MTA if we're
// running as a packaged application. The Windows.Storage APIs require a
// MTA. If this isn't happening during startup, it'll need to happen on
// a background thread.
void AppLogic::ReloadSettings()
void AppLogic::_ReloadSettings()
{
// Attempt to load our settings.
// If it fails,
@@ -1042,28 +1064,11 @@ namespace winrt::TerminalApp::implementation
// - display a loading error
_settingsLoadedResult = _TryLoadSettings();
const auto initialLoad = !_loadedInitialSettings;
_loadedInitialSettings = true;
if (FAILED(_settingsLoadedResult))
{
if (initialLoad)
{
_settings = CascadiaSettings::LoadDefaults();
}
else
{
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
return;
}
}
if (initialLoad)
{
// Register for directory change notification.
_RegisterSettingsChange();
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
return;
}
@@ -1370,7 +1375,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return AppLogic::_doFindTargetWindow(args, _settings.GlobalSettings().WindowingBehavior());
@@ -1527,7 +1532,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return _settings.GlobalSettings().AutoHideWindow();
@@ -1548,7 +1553,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false;
@@ -1661,7 +1666,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return _settings.GlobalSettings().MinimizeToNotificationArea();
@@ -1672,7 +1677,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return _settings.GlobalSettings().AlwaysShowNotificationIcon();
@@ -1688,7 +1693,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
ReloadSettings();
LoadSettings();
}
return _settings.GlobalSettings().CurrentTheme();
}

View File

@@ -62,8 +62,7 @@ namespace winrt::TerminalApp::implementation
bool IsUwp() const noexcept;
void RunAsUwp();
bool IsElevated() const noexcept;
void ReloadSettings();
void LoadSettings();
[[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept;
void Quit();
@@ -196,6 +195,7 @@ namespace winrt::TerminalApp::implementation
void _ProcessLazySettingsChanges();
void _RegisterSettingsChange();
fire_and_forget _DispatchReloadSettings();
void _ReloadSettings();
void _OpenSettingsUI();
bool _hasCommandLineArguments{ false };

View File

@@ -59,7 +59,7 @@ namespace TerminalApp
void Quit();
void ReloadSettings();
void LoadSettings();
Windows.UI.Xaml.UIElement GetRoot();
void SetInboundListener();

View File

@@ -213,6 +213,21 @@ namespace winrt::TerminalApp::implementation
_scrollToIndex(_filteredActionsView().Items().Size() - 1);
}
Windows::UI::Xaml::FrameworkElement CommandPalette::SelectedItem()
{
// auto item = _filteredActionsView().SelectedItem();
// auto container = _filteredActionsView().ContainerFromItem(item);
// auto itemFwe = item.try_as<Windows::UI::Xaml::FrameworkElement>();
// auto containerFwe = container.try_as<Windows::UI::Xaml::FrameworkElement>();
// itemFwe;
// return containerFwe;
auto index = _filteredActionsView().SelectedIndex();
const auto container = _filteredActionsView().ContainerFromIndex(index);
const auto item = container.try_as<winrt::Windows::UI::Xaml::Controls::ListViewItem>();
return item;
}
// Method Description:
// - Called when the command selection changes. We'll use this in the tab
// switcher to "preview" tabs as the user navigates the list of tabs. To
@@ -225,17 +240,42 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_selectedCommandChanged(const IInspectable& /*sender*/,
const Windows::UI::Xaml::RoutedEventArgs& /*args*/)
{
const auto currentlyVisible{ Visibility() == Visibility::Visible };
const auto selectedCommand = _filteredActionsView().SelectedItem();
const auto filteredCommand{ selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>() };
DescriptionTip().IsOpen(false);
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"SelectedItem" });
if (_currentMode == CommandPaletteMode::TabSwitchMode)
{
_switchToTab(filteredCommand);
}
else if (_currentMode == CommandPaletteMode::ActionMode && filteredCommand != nullptr)
else if (_currentMode == CommandPaletteMode::ActionMode &&
filteredCommand != nullptr &&
currentlyVisible)
{
if (const auto actionPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
{
_PreviewActionHandlers(*this, actionPaletteItem.Command());
const auto& cmd = actionPaletteItem.Command();
_PreviewActionHandlers(*this, cmd);
if (!cmd.Description().empty())
{
// teaching tip kinda sucks. While we wait for it to not
// suck, just toss it at the bottom fo the window, by not
// settings a target or placement mode.
DescriptionTip().Target(_searchBox());
DescriptionTip().Title(cmd.Name());
DescriptionTip().Subtitle(cmd.Description());
DescriptionTip().IsOpen(true);
}
// else
// {
// }
}
}
else if (_currentMode == CommandPaletteMode::CommandlineMode)
@@ -956,6 +996,15 @@ namespace winrt::TerminalApp::implementation
{
_updateFilteredActions();
}
else if (_currentMode == CommandPaletteMode::ActionMode)
{
auto actions = _collectFilteredActions();
_filteredActions.Clear();
for (const auto& action : actions)
{
_filteredActions.Append(action);
}
}
}
// Method Description:
@@ -1083,7 +1132,9 @@ namespace winrt::TerminalApp::implementation
{
std::copy(begin(commandsToFilter), end(commandsToFilter), std::back_inserter(actions));
}
else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode)
else if (_currentMode == CommandPaletteMode::TabSearchMode ||
_currentMode == CommandPaletteMode::ActionMode ||
_currentMode == CommandPaletteMode::CommandlineMode)
{
for (const auto& action : commandsToFilter)
{
@@ -1369,4 +1420,30 @@ namespace winrt::TerminalApp::implementation
ApplicationState::SharedInstance().RecentCommands(single_threaded_vector(std::move(newRecentCommands)));
}
void CommandPalette::PositionManually(Windows::Foundation::Point origin, Windows::Foundation::Size size)
{
Controls::Grid::SetRow(_backdrop(), 0);
Controls::Grid::SetColumn(_backdrop(), 0);
Controls::Grid::SetRowSpan(_backdrop(), 2);
Controls::Grid::SetColumnSpan(_backdrop(), 3);
// Set thie Max* versions here, otherwise when there are few results,
// the cmdpal will _still_ be 300x300 and filled with empty space
_backdrop().MaxWidth(size.Width);
_backdrop().MaxHeight(size.Height);
_backdrop().HorizontalAlignment(HorizontalAlignment::Stretch);
_backdrop().VerticalAlignment(VerticalAlignment::Stretch);
// // We can fake this. We're only using this method for the autocomplete
// // version of the cmdpal. Set the BG to acrylic.
// const auto colorControlStyle{ Resources().Lookup(winrt::box_value(L"CommandPaletteAcrylicBackground")).as<Windows::UI::Xaml::Style>() };
// _backdrop().Style(colorControlStyle);
Windows::UI::Xaml::Thickness margins{};
margins.Left = origin.X;
margins.Top = origin.Y;
_backdrop().Margin(margins);
}
}

View File

@@ -46,6 +46,10 @@ namespace winrt::TerminalApp::implementation
void EnableTabSwitcherMode(const uint32_t startIdx, Microsoft::Terminal::Settings::Model::TabSwitcherMode tabSwitcherMode);
void EnableTabSearchMode();
void PositionManually(Windows::Foundation::Point origin, Windows::Foundation::Size size);
Windows::UI::Xaml::FrameworkElement SelectedItem();
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SearchBoxPlaceholderText, _PropertyChangedHandlers);

View File

@@ -33,9 +33,13 @@ namespace TerminalApp
void EnableTabSwitcherMode(UInt32 startIdx, Microsoft.Terminal.Settings.Model.TabSwitcherMode tabSwitcherMode);
void EnableTabSearchMode();
void PositionManually(Windows.Foundation.Point origin, Windows.Foundation.Size size);
event Windows.Foundation.TypedEventHandler<CommandPalette, TabBase> SwitchToTabRequested;
event Windows.Foundation.TypedEventHandler<CommandPalette, Microsoft.Terminal.Settings.Model.Command> DispatchCommandRequested;
event Windows.Foundation.TypedEventHandler<CommandPalette, String> CommandLineExecutionRequested;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.Command> PreviewAction;
Windows.UI.Xaml.FrameworkElement SelectedItem { get; };
}
}

View File

@@ -215,7 +215,6 @@
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
@@ -243,7 +242,6 @@
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
@@ -271,7 +269,6 @@
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border" />
@@ -405,6 +402,16 @@
SelectionChanged="_listItemSelectionChanged"
SelectionMode="Single" />
<mux:TeachingTip x:Name="DescriptionTip"
Title=""
IsOpen="False"
PreferredPlacement="Right"
Subtitle="">
<!-- <muxc:TeachingTip.IconSource>
<muxc:SymbolIconSource Symbol="Refresh" />
</muxc:TeachingTip.IconSource>-->
</mux:TeachingTip>
</Grid>

View File

@@ -5,8 +5,6 @@
#include "Pane.h"
#include "AppLogic.h"
#include "Utils.h"
#include <Mmsystem.h>
using namespace winrt::Windows::Foundation;
@@ -46,7 +44,14 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
_warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
_closeTerminalRequestedToken = _control.CloseTerminalRequested({ this, &Pane::_CloseTerminalRequestedHandler });
// On the first Pane's creation, lookup resources we'll use to theme the
// Pane, including the brushed to use for the focused/unfocused border
// color.
if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr)
{
_SetupResources();
}
// Register an event with the control to have it inform us when it gains focus.
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
@@ -988,7 +993,7 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio
// actually no longer _our_ control, and instead could be a descendant.
//
// When the control's new Pane takes ownership of the control, the new
// parent will register its own event handler. That event handler will get
// parent will register it's own event handler. That event handler will get
// fired after this handler returns, and will properly cleanup state.
if (!_IsLeaf())
{
@@ -1031,26 +1036,6 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio
}
}
void Pane::_CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
std::unique_lock lock{ _createCloseLock };
// It's possible that this event handler started being executed, then before
// we got the lock, another thread created another child. So our control is
// actually no longer _our_ control, and instead could be a descendant.
//
// When the control's new Pane takes ownership of the control, the new
// parent will register its own event handler. That event handler will get
// fired after this handler returns, and will properly cleanup state.
if (!_IsLeaf())
{
return;
}
Close();
}
winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
{
auto weakThis{ weak_from_this() };
@@ -1296,7 +1281,7 @@ TermControl Pane::GetTerminalControl()
}
// Method Description:
// - Recursively remove the "Active" state from this Pane and all its children.
// - Recursively remove the "Active" state from this Pane and all it's children.
// - Updates our visuals to match our new state, including highlighting our borders.
// Arguments:
// - <none>
@@ -1601,7 +1586,6 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
// Add our new event handler before revoking the old one.
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
_warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
_closeTerminalRequestedToken = _control.CloseTerminalRequested({ this, &Pane::_CloseTerminalRequestedHandler });
// Revoke the old event handlers. Remove both the handlers for the panes
// themselves closing, and remove their handlers for their controls
@@ -1617,7 +1601,6 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
{
p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
p->_control.WarningBell(p->_warningBellToken);
p->_control.CloseTerminalRequested(p->_closeTerminalRequestedToken);
}
});
}
@@ -1626,7 +1609,6 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
remainingChild->Closed(remainingChildClosedToken);
remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
remainingChild->_control.WarningBell(remainingChild->_warningBellToken);
remainingChild->_control.CloseTerminalRequested(remainingChild->_closeTerminalRequestedToken);
// If we or either of our children was focused, we want to take that
// focus from them.
@@ -1708,7 +1690,6 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
{
p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
p->_control.WarningBell(p->_warningBellToken);
p->_control.CloseTerminalRequested(p->_closeTerminalRequestedToken);
}
});
}
@@ -2467,8 +2448,6 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
_connectionStateChangedToken.value = 0;
_control.WarningBell(_warningBellToken);
_warningBellToken.value = 0;
_control.CloseTerminalRequested(_closeTerminalRequestedToken);
_closeTerminalRequestedToken.value = 0;
// Remove our old GotFocus handler from the control. We don't want the
// control telling us that it's now focused, we want it telling its new
@@ -2572,7 +2551,7 @@ void Pane::Maximize(std::shared_ptr<Pane> zoomedPane)
}
// Always recurse into both children. If the (un)zoomed pane was one of
// our direct children, we'll still want to update its borders.
// our direct children, we'll still want to update it's borders.
_firstChild->Maximize(zoomedPane);
_secondChild->Maximize(zoomedPane);
}
@@ -2609,7 +2588,7 @@ void Pane::Restore(std::shared_ptr<Pane> zoomedPane)
}
// Always recurse into both children. If the (un)zoomed pane was one of
// our direct children, we'll still want to update its borders.
// our direct children, we'll still want to update it's borders.
_firstChild->Restore(zoomedPane);
_secondChild->Restore(zoomedPane);
}
@@ -3098,16 +3077,16 @@ float Pane::_ClampSplitPosition(const bool widthOrHeight, const float requestedV
// * The Brush we'll use for inactive Panes - TabViewBackground (to match the
// color of the titlebar)
// Arguments:
// - requestedTheme: this should be the currently active Theme for the app
// - <none>
// Return Value:
// - <none>
void Pane::SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
void Pane::_SetupResources()
{
const auto res = Application::Current().Resources();
const auto accentColorKey = winrt::box_value(L"SystemAccentColor");
if (res.HasKey(accentColorKey))
{
const auto colorFromResources = ThemeLookup(res, requestedTheme, accentColorKey);
const auto colorFromResources = res.Lookup(accentColorKey);
// If SystemAccentColor is _not_ a Color for some reason, use
// Transparent as the color, so we don't do this process again on
// the next pane (by leaving s_focusedBorderBrush nullptr)
@@ -3125,10 +3104,7 @@ void Pane::SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requeste
const auto unfocusedBorderBrushKey = winrt::box_value(L"UnfocusedBorderBrush");
if (res.HasKey(unfocusedBorderBrushKey))
{
// MAKE SURE TO USE ThemeLookup, so that we get the correct resource for
// the requestedTheme, not just the value from the resources (which
// might not respect the settings' requested theme)
auto obj = ThemeLookup(res, requestedTheme, unfocusedBorderBrushKey);
auto obj = res.Lookup(unfocusedBorderBrushKey);
s_unfocusedBorderBrush = obj.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
}
else

View File

@@ -136,8 +136,6 @@ public:
bool ContainsReadOnly() const;
static void SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
// Method Description:
// - A helper method for ad-hoc recursion on a pane tree. Walks the pane
// tree, calling a function on each pane in a depth-first pattern.
@@ -239,7 +237,6 @@ private:
winrt::event_token _firstClosedToken{ 0 };
winrt::event_token _secondClosedToken{ 0 };
winrt::event_token _warningBellToken{ 0 };
winrt::event_token _closeTerminalRequestedToken{ 0 };
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
@@ -293,7 +290,6 @@ private:
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
@@ -341,6 +337,8 @@ private:
return false;
}
static void _SetupResources();
struct PanePoint
{
float x;

View File

@@ -232,19 +232,19 @@
<value>Warnings were found while parsing your keybindings:</value>
</data>
<data name="TooManyKeysForChord" xml:space="preserve">
<value> Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
<comment>{Locked="\"keys\"",""} This glyph is a bullet, used in a bulleted list.</comment>
<value>&#x2022; Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
<comment>{Locked="\"keys\"","&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="FailedToParseSubCommands" xml:space="preserve">
<value> Failed to parse all subcommands of nested command.</value>
<value>&#x2022; Failed to parse all subcommands of nested command.</value>
</data>
<data name="MissingRequiredParameter" xml:space="preserve">
<value> Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked=""} This glyph is a bullet, used in a bulleted list.</comment>
<value>&#x2022; Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="UnknownTheme" xml:space="preserve">
<value> The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked=""} This glyph is a bullet, used in a bulleted list.</comment>
<value>&#x2022; The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="LegacyGlobalsProperty" xml:space="preserve">
<value>The "globals" property is deprecated - your settings might need updating. </value>
@@ -752,9 +752,17 @@
<value>Suggestions found: {0}</value>
<comment>{0} will be replaced with a number.</comment>
</data>
<data name="DuplicateRemainingProfilesEntry" xml:space="preserve">
<value>The "newTabMenu" field contains more than one entry of type "remainingProfiles". Only the first one will be considered.</value>
<comment>{Locked="newTabMenu"} {Locked="remainingProfiles"}</comment>
<data name="SuggestionTest.Title" xml:space="preserve">
<value>Get suggestions</value>
</data>
<data name="SuggestionTest.Subtitle" xml:space="preserve">
<value>Enter a prompt:</value>
</data>
<data name="SuggestionTest.ActionButtonContent" xml:space="preserve">
<value>Submit</value>
</data>
<data name="SuggestionTest.CloseButtonContent" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="AboutToolTip" xml:space="preserve">
<value>Open a dialog containing product information</value>
@@ -792,7 +800,4 @@
<data name="TabCloseToolTip" xml:space="preserve">
<value>Close this tab</value>
</data>
<data name="NewTabMenuFolderEmpty" xml:space="preserve">
<value>Empty...</value>
</data>
</root>
</root>

View File

@@ -356,12 +356,16 @@ namespace winrt::TerminalApp::implementation
Media::SolidColorBrush hoverTabBrush{};
Media::SolidColorBrush subtleFillColorSecondaryBrush;
Media::SolidColorBrush subtleFillColorTertiaryBrush;
// calculate the luminance of the current color and select a font
// color based on that
// see https://www.w3.org/TR/WCAG20/#relativeluminancedef
if (TerminalApp::ColorHelper::IsBrightColor(color))
{
fontBrush.Color(winrt::Windows::UI::Colors::Black());
auto secondaryFontColor = winrt::Windows::UI::Colors::Black();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269
secondaryFontColor.A = 0x9E;
secondaryFontBrush.Color(secondaryFontColor);
auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black();
subtleFillColorSecondary.A = 0x09;
subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
@@ -371,6 +375,11 @@ namespace winrt::TerminalApp::implementation
}
else
{
fontBrush.Color(winrt::Windows::UI::Colors::White());
auto secondaryFontColor = winrt::Windows::UI::Colors::White();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14
secondaryFontColor.A = 0xC5;
secondaryFontBrush.Color(secondaryFontColor);
auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White();
subtleFillColorSecondary.A = 0x0F;
subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
@@ -379,25 +388,6 @@ namespace winrt::TerminalApp::implementation
subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary);
}
// The tab font should be based on the evaluated appearance of the tab color layered on tab row.
const auto layeredTabColor = til::color{ color }.layer_over(_tabRowColor);
if (TerminalApp::ColorHelper::IsBrightColor(layeredTabColor))
{
fontBrush.Color(winrt::Windows::UI::Colors::Black());
auto secondaryFontColor = winrt::Windows::UI::Colors::Black();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269
secondaryFontColor.A = 0x9E;
secondaryFontBrush.Color(secondaryFontColor);
}
else
{
fontBrush.Color(winrt::Windows::UI::Colors::White());
auto secondaryFontColor = winrt::Windows::UI::Colors::White();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14
secondaryFontColor.A = 0xC5;
secondaryFontBrush.Color(secondaryFontColor);
}
selectedTabBrush.Color(color);
// Start with the current tab color, set to Opacity=.3

View File

@@ -95,22 +95,17 @@ namespace winrt::TerminalApp::implementation
// - Sets up state, event handlers, etc on a tab object that was just made.
// Arguments:
// - newTabImpl: the uninitialized tab.
// - insertPosition: Optional parameter to indicate the position of tab.
void TerminalPage::_InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl, uint32_t insertPosition)
void TerminalPage::_InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl)
{
newTabImpl->Initialize();
// If insert position is not passed, calculate it
if (insertPosition == -1)
uint32_t insertPosition = _tabs.Size();
if (_settings.GlobalSettings().NewTabPosition() == NewTabPosition::AfterCurrentTab)
{
insertPosition = _tabs.Size();
if (_settings.GlobalSettings().NewTabPosition() == NewTabPosition::AfterCurrentTab)
auto currentTabIndex = _GetFocusedTabIndex();
if (currentTabIndex.has_value())
{
auto currentTabIndex = _GetFocusedTabIndex();
if (currentTabIndex.has_value())
{
insertPosition = currentTabIndex.value() + 1;
}
insertPosition = currentTabIndex.value() + 1;
}
}
@@ -194,9 +189,9 @@ namespace winrt::TerminalApp::implementation
if (page && tab)
{
// Passing empty string as the path to export tab will make it
// prompt for the path
page->_ExportTab(*tab, L"");
// Passing null args to the ExportBuffer handler will default it
// to prompting for the path
page->_HandleExportBuffer(nullptr, nullptr);
}
});
@@ -206,8 +201,7 @@ namespace winrt::TerminalApp::implementation
if (page && tab)
{
page->_SetFocusedTab(*tab);
page->_Find(*tab);
page->_Find();
}
});
@@ -265,13 +259,12 @@ namespace winrt::TerminalApp::implementation
// - Create a new tab using a specified pane as the root.
// Arguments:
// - pane: The pane to use as the root.
// - insertPosition: Optional parameter to indicate the position of tab.
void TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition)
void TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane)
{
if (pane)
{
auto newTabImpl = winrt::make_self<TerminalTab>(pane);
_InitializeTab(newTabImpl, insertPosition);
_InitializeTab(newTabImpl);
}
}
@@ -344,7 +337,7 @@ namespace winrt::TerminalApp::implementation
// In the future, it may be preferable to just duplicate the
// current control's live settings (which will include changes
// made through VT).
_CreateNewTabFromPane(_MakePane(nullptr, tab, nullptr), tab.TabViewIndex() + 1);
_CreateNewTabFromPane(_MakePane(nullptr, tab, nullptr));
const auto runtimeTabText{ tab.GetTabText() };
if (!runtimeTabText.empty())

View File

@@ -19,6 +19,7 @@
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
<TerminalMUX>true</TerminalMUX>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
@@ -381,14 +382,6 @@
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="$(WindowsSDK_MetadataPathVersioned)\Windows.UI.Xaml.Hosting.HostingContract\*\*.winmd">
<WinMDFile>true</WinMDFile>
<CopyLocal>false</CopyLocal>
<ReferenceGrouping>$(TargetPlatformMoniker)</ReferenceGrouping>
<ReferenceGroupingDisplayName>$(TargetPlatformDisplayName)</ReferenceGroupingDisplayName>
<ResolvedFrom>CppWinRTImplicitlyExpandTargetPlatform</ResolvedFrom>
<IsSystemReference>True</IsSystemReference>
</Reference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
@@ -411,7 +404,7 @@
<!--
By default, the PRI file will contain resource paths beginning with the
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
Well, App.xbf is hard-coded by the framework to be found at the resource ROOT.
Well, App.xbf is hardcoded by the framework to be found at the resource ROOT.
To make that happen, we have to disable the prepending of the project name
to the App xaml files.
-->

View File

@@ -287,6 +287,17 @@ namespace winrt::TerminalApp::implementation
CommandPalette().SwitchToTabRequested({ this, &TerminalPage::_OnSwitchToTabRequested });
CommandPalette().PreviewAction({ this, &TerminalPage::_PreviewActionHandler });
AutoCompleteMenu().PositionManually(Windows::Foundation::Point{ 0, 0 }, Windows::Foundation::Size{ 200, 300 });
AutoCompleteMenu().RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
if (AutoCompleteMenu().Visibility() == Visibility::Collapsed)
{
AutoCompletePopup().IsOpen(false);
_FocusActiveControl(nullptr, nullptr);
}
});
AutoCompleteMenu().DispatchCommandRequested({ this, &TerminalPage::_OnDispatchCommandRequested });
AutoCompleteMenu().PreviewAction({ this, &TerminalPage::_PreviewActionHandler });
// Settings AllowDependentAnimations will affect whether animations are
// enabled application-wide, so we don't need to check it each time we
// want to create an animation.
@@ -824,14 +835,79 @@ namespace winrt::TerminalApp::implementation
auto newTabFlyout = WUX::Controls::MenuFlyout{};
newTabFlyout.Placement(WUX::Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedLeft);
// Create profile entries from the NewTabMenu configuration using a
// recursive helper function. This returns a std::vector of FlyoutItemBases,
// that we then add to our Flyout.
auto entries = _settings.GlobalSettings().NewTabMenu();
auto items = _CreateNewTabFlyoutItems(entries);
for (const auto& item : items)
auto actionMap = _settings.ActionMap();
const auto defaultProfileGuid = _settings.GlobalSettings().DefaultProfile();
// the number of profiles should not change in the loop for this to work
const auto profileCount = gsl::narrow_cast<int>(_settings.ActiveProfiles().Size());
for (auto profileIndex = 0; profileIndex < profileCount; profileIndex++)
{
newTabFlyout.Items().Append(item);
const auto profile = _settings.ActiveProfiles().GetAt(profileIndex);
auto profileMenuItem = WUX::Controls::MenuFlyoutItem{};
// Add the keyboard shortcuts based on the number of profiles defined
// Look for a keychord that is bound to the equivalent
// NewTab(ProfileIndex=N) action
NewTerminalArgs newTerminalArgs{ profileIndex };
NewTabArgs newTabArgs{ newTerminalArgs };
auto profileKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
// make sure we find one to display
if (profileKeyChord)
{
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
}
auto profileName = profile.Name();
profileMenuItem.Text(profileName);
// If there's an icon set for this profile, set it as the icon for
// this flyout item.
if (!profile.Icon().empty())
{
auto icon = IconPathConverter::IconWUX(profile.Icon());
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);
profileMenuItem.Icon(icon);
}
if (profile.Guid() == defaultProfileGuid)
{
// Contrast the default profile with others in font weight.
profileMenuItem.FontWeight(FontWeights::Bold());
}
auto newTabRun = WUX::Documents::Run();
newTabRun.Text(RS_(L"NewTabRun/Text"));
auto newPaneRun = WUX::Documents::Run();
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
newPaneRun.FontStyle(FontStyle::Italic);
auto newWindowRun = WUX::Documents::Run();
newWindowRun.Text(RS_(L"NewWindowRun/Text"));
newWindowRun.FontStyle(FontStyle::Italic);
auto elevatedRun = WUX::Documents::Run();
elevatedRun.Text(RS_(L"ElevatedRun/Text"));
elevatedRun.FontStyle(FontStyle::Italic);
auto textBlock = WUX::Controls::TextBlock{};
textBlock.Inlines().Append(newTabRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newPaneRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newWindowRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(elevatedRun);
auto toolTip = WUX::Controls::ToolTip{};
toolTip.Content(textBlock);
WUX::Controls::ToolTipService::SetToolTip(profileMenuItem, toolTip);
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
NewTerminalArgs newTerminalArgs{ profileIndex };
page->_OpenNewTerminalViaDropdown(newTerminalArgs);
}
});
newTabFlyout.Items().Append(profileMenuItem);
}
// add menu separator
@@ -867,7 +943,6 @@ namespace winrt::TerminalApp::implementation
settingsItem.Click({ this, &TerminalPage::_SettingsButtonOnClick });
newTabFlyout.Items().Append(settingsItem);
auto actionMap = _settings.ActionMap();
const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) };
if (settingsKeyChord)
{
@@ -927,215 +1002,6 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Flyout(newTabFlyout);
}
// Method Description:
// - For a given list of tab menu entries, this method will create the corresponding
// list of flyout items. This is a recursive method that calls itself when it comes
// across a folder entry.
std::vector<WUX::Controls::MenuFlyoutItemBase> TerminalPage::_CreateNewTabFlyoutItems(IVector<NewTabMenuEntry> entries)
{
std::vector<WUX::Controls::MenuFlyoutItemBase> items;
if (entries == nullptr || entries.Size() == 0)
{
return items;
}
for (const auto& entry : entries)
{
if (entry == nullptr)
{
continue;
}
switch (entry.Type())
{
case NewTabMenuEntryType::Separator:
{
items.push_back(WUX::Controls::MenuFlyoutSeparator{});
break;
}
// A folder has a custom name and icon, and has a number of entries that require
// us to call this method recursively.
case NewTabMenuEntryType::Folder:
{
const auto folderEntry = entry.as<FolderEntry>();
const auto folderEntries = folderEntry.Entries();
// If the folder is empty, we should skip the entry if AllowEmpty is false, or
// when the folder should inline.
// The IsEmpty check includes semantics for nested (empty) folders
if (folderEntries.Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
{
break;
}
// Recursively generate flyout items
auto folderEntryItems = _CreateNewTabFlyoutItems(folderEntries);
// If the folder should auto-inline and there is only one item, do so.
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntries.Size() == 1)
{
for (auto const& folderEntryItem : folderEntryItems)
{
items.push_back(folderEntryItem);
}
break;
}
// Otherwise, create a flyout
auto folderItem = WUX::Controls::MenuFlyoutSubItem{};
folderItem.Text(folderEntry.Name());
auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon());
folderItem.Icon(icon);
for (const auto& folderEntryItem : folderEntryItems)
{
folderItem.Items().Append(folderEntryItem);
}
// If the folder is empty, and by now we know we set AllowEmpty to true,
// create a placeholder item here
if (folderEntries.Size() == 0)
{
auto placeholder = WUX::Controls::MenuFlyoutItem{};
placeholder.Text(RS_(L"NewTabMenuFolderEmpty"));
placeholder.IsEnabled(false);
folderItem.Items().Append(placeholder);
}
items.push_back(folderItem);
break;
}
// Any "collection entry" will simply make us add each profile in the collection
// separately. This collection is stored as a map <int, Profile>, so the correct
// profile index is already known.
case NewTabMenuEntryType::RemainingProfiles:
case NewTabMenuEntryType::MatchProfiles:
{
const auto remainingProfilesEntry = entry.as<ProfileCollectionEntry>();
if (remainingProfilesEntry.Profiles() == nullptr)
{
break;
}
for (auto&& [profileIndex, remainingProfile] : remainingProfilesEntry.Profiles())
{
items.push_back(_CreateNewTabFlyoutProfile(remainingProfile, profileIndex));
}
break;
}
// A single profile, the profile index is also given in the entry
case NewTabMenuEntryType::Profile:
{
const auto profileEntry = entry.as<ProfileEntry>();
if (profileEntry.Profile() == nullptr)
{
break;
}
auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex());
items.push_back(profileItem);
break;
}
}
}
return items;
}
// Method Description:
// - This method creates a flyout menu item for a given profile with the given index.
// It makes sure to set the correct icon, keybinding, and click-action.
WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutProfile(const Profile profile, int profileIndex)
{
auto profileMenuItem = WUX::Controls::MenuFlyoutItem{};
// Add the keyboard shortcuts based on the number of profiles defined
// Look for a keychord that is bound to the equivalent
// NewTab(ProfileIndex=N) action
NewTerminalArgs newTerminalArgs{ profileIndex };
NewTabArgs newTabArgs{ newTerminalArgs };
auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
// make sure we find one to display
if (profileKeyChord)
{
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
}
auto profileName = profile.Name();
profileMenuItem.Text(profileName);
// If there's an icon set for this profile, set it as the icon for
// this flyout item
if (!profile.Icon().empty())
{
const auto icon = _CreateNewTabFlyoutIcon(profile.Icon());
profileMenuItem.Icon(icon);
}
if (profile.Guid() == _settings.GlobalSettings().DefaultProfile())
{
// Contrast the default profile with others in font weight.
profileMenuItem.FontWeight(FontWeights::Bold());
}
auto newTabRun = WUX::Documents::Run();
newTabRun.Text(RS_(L"NewTabRun/Text"));
auto newPaneRun = WUX::Documents::Run();
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
newPaneRun.FontStyle(FontStyle::Italic);
auto newWindowRun = WUX::Documents::Run();
newWindowRun.Text(RS_(L"NewWindowRun/Text"));
newWindowRun.FontStyle(FontStyle::Italic);
auto elevatedRun = WUX::Documents::Run();
elevatedRun.Text(RS_(L"ElevatedRun/Text"));
elevatedRun.FontStyle(FontStyle::Italic);
auto textBlock = WUX::Controls::TextBlock{};
textBlock.Inlines().Append(newTabRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newPaneRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newWindowRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(elevatedRun);
auto toolTip = WUX::Controls::ToolTip{};
toolTip.Content(textBlock);
WUX::Controls::ToolTipService::SetToolTip(profileMenuItem, toolTip);
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
NewTerminalArgs newTerminalArgs{ profileIndex };
page->_OpenNewTerminalViaDropdown(newTerminalArgs);
}
});
return profileMenuItem;
}
// Method Description:
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
// MenuFlyoutSubItems
IconElement TerminalPage::_CreateNewTabFlyoutIcon(const winrt::hstring& iconSource)
{
if (iconSource.empty())
{
return nullptr;
}
auto icon = IconPathConverter::IconWUX(iconSource);
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);
return icon;
}
// Function Description:
// Called when the openNewTabDropdown keybinding is used.
// Shows the dropdown flyout.
@@ -1290,7 +1156,7 @@ namespace winrt::TerminalApp::implementation
// The connection must be informed of the current CWD on
// construction, because the connection might not spawn the child
// process until later, on another thread, after we've already
// restored the CWD to its original value.
// restored the CWD to it's original value.
auto newWorkingDirectory{ settings.StartingDirectory() };
if (newWorkingDirectory.size() == 0 || newWorkingDirectory.size() == 1 &&
!(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
@@ -1462,7 +1328,13 @@ namespace winrt::TerminalApp::implementation
return;
}
if (const auto p = CommandPalette(); p.Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
if (const auto p = CommandPalette(); p.Visibility() == Visibility::Visible &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
{
p.Visibility(Visibility::Collapsed);
}
if (const auto p = AutoCompleteMenu(); p.Visibility() == Visibility::Visible &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
{
p.Visibility(Visibility::Collapsed);
}
@@ -1653,6 +1525,8 @@ namespace winrt::TerminalApp::implementation
});
term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
term.MenuChanged({ get_weak(), &TerminalPage::_ControlMenuChangedHandler });
}
// Method Description:
@@ -2255,7 +2129,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Place `copiedData` into the clipboard as text. Triggered when a
// terminal control raises its CopyToClipboard event.
// terminal control raises it's CopyToClipboard event.
// Arguments:
// - copiedData: the new string content to place on the clipboard.
winrt::fire_and_forget TerminalPage::_CopyToClipboardHandler(const IInspectable /*sender*/,
@@ -2760,12 +2634,6 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_SetBackgroundImage(const winrt::Microsoft::Terminal::Settings::Model::IAppearanceConfig& newAppearance)
{
if (!_settings.GlobalSettings().UseBackgroundImageForWindow())
{
_tabContent.Background(nullptr);
return;
}
const auto path = newAppearance.ExpandedBackgroundImagePath();
if (path.empty())
{
@@ -2803,14 +2671,9 @@ namespace winrt::TerminalApp::implementation
Media::Imaging::BitmapImage image(imageUri);
b.ImageSource(image);
_tabContent.Background(b);
}
// Pull this into a separate block. If the image didn't change, but the
// properties of the image did, we should still update them.
if (const auto newBrush{ _tabContent.Background().try_as<Media::ImageBrush>() })
{
newBrush.Stretch(newAppearance.BackgroundImageStretchMode());
newBrush.Opacity(newAppearance.BackgroundImageOpacity());
b.Stretch(newAppearance.BackgroundImageStretchMode());
b.Opacity(newAppearance.BackgroundImageOpacity());
}
}
@@ -2844,6 +2707,18 @@ namespace winrt::TerminalApp::implementation
profileGuidSettingsMap.insert_or_assign(newProfile.Guid(), std::pair{ newProfile, nullptr });
}
if (_settings.GlobalSettings().UseBackgroundImageForWindow())
{
const auto focusedTab{ _GetFocusedTabImpl() };
if (focusedTab)
{
auto profile = focusedTab->GetFocusedProfile();
if (profile)
{
_SetBackgroundImage(profile.DefaultAppearance());
}
}
}
for (const auto& tab : _tabs)
{
if (auto terminalTab{ _GetTerminalTabImpl(tab) })
@@ -2889,14 +2764,6 @@ namespace winrt::TerminalApp::implementation
tabImpl->SetActionMap(_settings.ActionMap());
}
if (const auto focusedTab{ _GetFocusedTabImpl() })
{
if (const auto profile{ focusedTab->GetFocusedProfile() })
{
_SetBackgroundImage(profile.DefaultAppearance());
}
}
// repopulate the new tab button's flyout with entries for each
// profile, which might have changed
_UpdateTabWidthMode();
@@ -3160,15 +3027,15 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Called when the user tries to do a search using keybindings.
// This will tell the active terminal control of the passed tab
// to create a search box and enable find process.
// This will tell the current focused terminal control to create
// a search box and enable find process.
// Arguments:
// - tab: the tab where the search box should be created
// - <none>
// Return Value:
// - <none>
void TerminalPage::_Find(const TerminalTab& tab)
void TerminalPage::_Find()
{
if (const auto& control{ tab.GetActiveTerminalControl() })
if (const auto& control{ _GetActiveControl() })
{
control.CreateSearchBoxControl();
}
@@ -3895,7 +3762,6 @@ namespace winrt::TerminalApp::implementation
}
}
}
// WindowId is a otherwise generic WINRT_OBSERVABLE_PROPERTY, but it needs
// to raise a PropertyChanged for WindowIdForDisplay, instead of
// WindowId.
@@ -4320,22 +4186,6 @@ namespace winrt::TerminalApp::implementation
const auto theme = _settings.GlobalSettings().CurrentTheme();
auto requestedTheme{ theme.RequestedTheme() };
{
// Update the brushes that Pane's use...
Pane::SetupResources(requestedTheme);
// ... then trigger a visual update for all the pane borders to
// apply the new ones.
for (const auto& tab : _tabs)
{
if (auto terminalTab{ _GetTerminalTabImpl(tab) })
{
terminalTab->GetRootPane()->WalkTree([&](auto&& pane) {
pane->UpdateVisuals();
});
}
}
}
const auto res = Application::Current().Resources();
// Use our helper to lookup the theme-aware version of the resource.
@@ -4445,4 +4295,235 @@ namespace winrt::TerminalApp::implementation
_activated = activated;
_updateThemeColors();
}
winrt::fire_and_forget TerminalPage::_ControlMenuChangedHandler(const IInspectable /*sender*/,
const winrt::Microsoft::Terminal::Control::MenuChangedEventArgs args)
{
// co_await winrt::resume_background();
// May be able to fake this by not creating whole Commands for these
// actions, instead just binding them at the cmdpal layer (like tab item
// vs action item)
// auto entries = control.MenuEntries();
// parse json
auto commandsCollection = Command::ParsePowerShellMenuComplete(args.MenuJson(), args.ReplacementLength());
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
_openTaskView(commandsCollection);
}
void TerminalPage::_openTaskView(const Windows::Foundation::Collections::IVector<Command>& commandSource)
{
auto control{ _GetActiveControl() };
if (!control)
{
return;
}
if (commandSource.Size() == 0)
{
AutoCompletePopup().IsOpen(false);
AutoCompleteMenu().Visibility(Visibility::Collapsed);
return;
}
// CommandPalette has an internal margin of 8, so set to -4,-4 to position closer to the actual line
AutoCompleteMenu().PositionManually(Windows::Foundation::Point{ -4, -4 }, Windows::Foundation::Size{ 300, 300 });
// CommandPalette().EnableCommandPaletteMode(CommandPaletteLaunchMode::Action);
const til::point cursorPos{ control.CursorPositionInDips() };
const auto characterSize{ control.CharacterDimensions() };
// Position relative to the actual term control
AutoCompletePopup().HorizontalOffset(cursorPos.x);
AutoCompletePopup().VerticalOffset(cursorPos.y + characterSize.Height);
AutoCompletePopup().IsOpen(true);
// ~Make visible first, then set commands. Other way around and the list
// doesn't actually update the first time (weird)~
AutoCompleteMenu().SetCommands(commandSource);
AutoCompleteMenu().Visibility(commandSource.Size() > 0 ? Visibility::Visible : Visibility::Collapsed);
}
//////////////////////////
#pragma region SuggestionTeachingTip
void TerminalPage::_openSuggestionsPrompt()
{
if (SuggestionTest() == nullptr)
{
// We need to use FindName to lazy-load this object
if (auto tip{ FindName(L"SuggestionTest").try_as<MUX::Controls::TeachingTip>() })
{
tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl });
}
}
_UpdateTeachingTipTheme(SuggestionTest().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
SuggestionResults().Text(L"");
SuggestionTest().IsOpen(true);
}
void TerminalPage::_SuggestionActionClick(const IInspectable& /*sender*/,
const IInspectable& /*eventArgs*/)
{
auto prompt = SuggestionTextBox().Text();
prompt;
// TODO! get the buffer history. An idea might be to take just the lines
// that have prompt marks on them. That might be an easy way to fake
// shell integration, at least for the prototype. That would probably
// require TermControl to expose that.
// TODO! Take the prompt and the buffer history and plumb it into the suggestion provider. How?
auto control{ _GetActiveControl() };
if (!control)
{
// args.Handled(false);
return;
}
const auto buffer = control.ReadEntireBuffer();
buffer;
SuggestionResults().Text(buffer);
// An idea: export the text to a temp file, then run a command like `get-suggestions.py temp.txt` and read that output.
// CascadiaSettings::ExportFile(tempPath, buffer);
// Launch foo.exe and get the output
// {
// wil::unique_handle readPipe;
// wil::unique_handle writePipe;
// SECURITY_ATTRIBUTES sa{ sizeof(sa), nullptr, true };
// THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&readPipe, &writePipe, &sa, 0));
// STARTUPINFO si{ 0 };
// si.cb = sizeof(si);
// si.dwFlags = STARTF_USESTDHANDLES;
// si.hStdOutput = writePipe.get();
// si.hStdError = writePipe.get();
// wil::unique_process_information pi;
// // wil::unique_cotaskmem_string systemPath;
// // THROW_IF_FAILED(wil::GetSystemDirectoryW(systemPath));
// // std::wstring command(systemPath.get());
// std::wstring command = L"python.exe D:\\test.py ";
// THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(nullptr,
// const_cast<LPWSTR>(command.c_str()),
// nullptr,
// nullptr,
// TRUE,
// CREATE_NO_WINDOW,
// nullptr,
// nullptr,
// &si,
// &pi));
// switch (WaitForSingleObject(pi.hProcess, 2000))
// {
// case WAIT_OBJECT_0:
// break;
// case WAIT_ABANDONED:
// case WAIT_TIMEOUT:
// return;
// case WAIT_FAILED:
// THROW_LAST_ERROR();
// default:
// THROW_HR(ERROR_UNHANDLED_EXCEPTION);
// }
// DWORD exitCode;
// if (!GetExitCodeProcess(pi.hProcess, &exitCode))
// {
// THROW_HR(E_INVALIDARG);
// }
// else if (exitCode != 0)
// {
// return;
// }
// DWORD bytesAvailable;
// THROW_IF_WIN32_BOOL_FALSE(PeekNamedPipe(readPipe.get(), nullptr, NULL, nullptr, &bytesAvailable, nullptr));
// // "The _open_osfhandle call transfers ownership of the Win32 file handle to the file descriptor."
// // (https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/open-osfhandle?view=vs-2019)
// // so, we detach_from_smart_pointer it -- but...
// // "File descriptors passed into _fdopen are owned by the returned FILE * stream.
// // If _fdopen is successful, do not call _close on the file descriptor.
// // Calling fclose on the returned FILE * also closes the file descriptor."
// // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fdopen-wfdopen?view=vs-2019
// auto stdioPipeHandle = _wfdopen(_open_osfhandle((intptr_t)wil::detach_from_smart_pointer(readPipe), _O_WTEXT | _O_RDONLY), L"r");
// auto closeFile = wil::scope_exit([&]() { fclose(stdioPipeHandle); });
// std::wfstream pipe{ stdioPipeHandle };
// std::wstring wline;
// std::getline(pipe, wline); // remove the header from the output.
// while (pipe.tellp() < bytesAvailable)
// {
// std::getline(pipe, wline);
// std::wstringstream wlinestream(wline);
// if (wlinestream)
// {
// std::wstring distName;
// std::getline(wlinestream, distName, L'\r');
// if (til::starts_with(distName, DockerDistributionPrefix))
// {
// // Docker for Windows creates some utility distributions to handle Docker commands.
// // Pursuant to GH#3556, because they are _not_ user-facing we want to hide them.
// continue;
// }
// const auto firstChar = distName.find_first_of(L"( ");
// // Some localizations don't have a space between the name and "(Default)"
// // https://github.com/microsoft/terminal/issues/1168#issuecomment-500187109
// if (firstChar < distName.size())
// {
// distName.resize(firstChar);
// }
// profiles.emplace_back(makeProfile(distName));
// }
// }
// }
}
void TerminalPage::_SuggestionKeyDown(const IInspectable& /*sender*/,
const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& /*e*/)
{
// const auto key = e.OriginalKey();
// if (key == Windows::System::VirtualKey::Enter)
// {
// _renamerPressedEnter = true;
// }
}
// Method Description:
// - Manually handle Enter and Escape for committing and dismissing a window
// rename. This is highly similar to the TabHeaderControl's KeyUp handler.
// Arguments:
// - e: the KeyRoutedEventArgs describing the key that was released
// Return Value:
// - <none>
void TerminalPage::_SuggestionKeyUp(const IInspectable& /*sender*/,
const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e)
{
const auto key = e.OriginalKey();
/*if (key == Windows::System::VirtualKey::Enter && _renamerPressedEnter)
{
// User is done making changes, close the rename box
_WindowRenamerActionClick(sender, nullptr);
}
else */
if (key == Windows::System::VirtualKey::Escape)
{
// User wants to discard the changes they made
WindowRenamerTextBox().Text(WindowName());
WindowRenamer().IsOpen(false);
// _renamerPressedEnter = false;
}
}
#pragma endregion
//////
}

View File

@@ -240,13 +240,9 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowLargePasteWarningDialog();
void _CreateNewTabFlyout();
std::vector<winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase> _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::NewTabMenuEntry> entries);
winrt::Windows::UI::Xaml::Controls::IconElement _CreateNewTabFlyoutIcon(const winrt::hstring& icon);
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition = -1);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
@@ -288,7 +284,7 @@ namespace winrt::TerminalApp::implementation
void _RemoveTab(const winrt::TerminalApp::TabBase& tab);
winrt::fire_and_forget _RemoveTabs(const std::vector<winrt::TerminalApp::TabBase> tabs);
void _InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl, uint32_t insertPosition = -1);
void _InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl);
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term);
void _RegisterTabEvents(TerminalTab& hostingTab);
@@ -388,7 +384,7 @@ namespace winrt::TerminalApp::implementation
void _OnCommandLineExecutionRequested(const IInspectable& sender, const winrt::hstring& commandLine);
void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::TabBase& tab);
void _Find(const TerminalTab& tab);
void _Find();
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
@@ -429,6 +425,7 @@ namespace winrt::TerminalApp::implementation
void _RunRestorePreviews();
void _PreviewColorScheme(const Microsoft::Terminal::Settings::Model::SetColorSchemeArgs& args);
void _PreviewAdjustOpacity(const Microsoft::Terminal::Settings::Model::AdjustOpacityArgs& args);
void _PreviewSendInput(const Microsoft::Terminal::Settings::Model::SendInputArgs& args);
winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs _lastPreviewedAction{ nullptr };
std::vector<std::function<void()>> _restorePreviewFuncs{};
@@ -440,6 +437,10 @@ namespace winrt::TerminalApp::implementation
void _WindowRenamerKeyDown(const IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _WindowRenamerKeyUp(const IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _SuggestionActionClick(const IInspectable& sender, const IInspectable& eventArgs);
void _SuggestionKeyDown(const IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _SuggestionKeyUp(const IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _UpdateTeachingTipTheme(winrt::Windows::UI::Xaml::FrameworkElement element);
winrt::Microsoft::Terminal::Settings::Model::Profile GetClosestProfileForDuplicationOfProfile(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile) const noexcept;
@@ -462,6 +463,10 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
winrt::fire_and_forget _ControlMenuChangedHandler(const winrt::Windows::Foundation::IInspectable sender, const winrt::Microsoft::Terminal::Control::MenuChangedEventArgs args);
void _openSuggestionsPrompt();
void _openTaskView(const Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::Command>& commandSource);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);

View File

@@ -82,7 +82,7 @@
<!--
GH#12775 et. al: After switching to ControlsV2, it seems that
delay-loading a dialog causes the ContentDialog to be assigned a
Height equal to its content size. If we DON'T assign the
Height equal to it's content size. If we DON'T assign the
ContentDialog a Row, I believe it's assigned Row 0 by default. So,
when the dialog gets opened, the dialog seemingly causes a giant
hole to appear in the body of the app.
@@ -195,6 +195,25 @@
PreviewKeyDown="_KeyDownHandler"
Visibility="Collapsed" />
<Popup x:Name="AutoCompletePopup"
HorizontalOffset="200"
IsOpen="False"
ShouldConstrainToRootBounds="False"
VerticalOffset="200">
<!--
TODO! POpup is hacky and does weird stuff with focus
Like, clicking on the suggest menu to focus the window doesn't focus the actual window, it focuses the popup (?)
Also, it causes the cmdpal to be above the TeachingTip. That... doesn't make sense, I know. But it happens.
-->
<local:CommandPalette x:Name="AutoCompleteMenu"
VerticalAlignment="Stretch"
PreviewKeyDown="_KeyDownHandler"
Visibility="Collapsed" />
</Popup>
<!--
A TeachingTip with IsLightDismissEnabled="True" will immediately
dismiss itself if the window is unfocused (In Xaml Islands). This is
@@ -225,5 +244,23 @@
Text="{x:Bind WindowName, Mode=OneWay}" />
</mux:TeachingTip.Content>
</mux:TeachingTip>
<mux:TeachingTip x:Name="SuggestionTest"
x:Uid="SuggestionTest"
x:Load="False"
ActionButtonClick="_SuggestionActionClick"
ActionButtonStyle="{ThemeResource AccentButtonStyle}"
IsLightDismissEnabled="False">
<mux:TeachingTip.Content>
<StackPanel Orientation="Vertical">
<TextBox x:Name="SuggestionTextBox"
KeyDown="_SuggestionKeyDown"
KeyUp="_SuggestionKeyUp"
Text="" />
<TextBlock x:Name="SuggestionResults" />
</StackPanel>
</mux:TeachingTip.Content>
</mux:TeachingTip>
</Grid>
</Page>

View File

@@ -14,6 +14,7 @@
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
<TerminalMUX>true</TerminalMUX>
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
@@ -70,7 +71,7 @@
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
<!-- Reference TerminalAppLib here, so we can use its TerminalApp.winmd as
<!-- Reference TerminalAppLib here, so we can use it's TerminalApp.winmd as
our TerminalApp.winmd. This didn't work correctly in VS2017, you'd need to
manually reference the lib -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalAppLib.vcxproj">

View File

@@ -50,6 +50,7 @@
#include <winrt/Windows.Media.Core.h>
#include <winrt/Windows.Media.Playback.h>
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

View File

@@ -9,7 +9,7 @@ using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
static til::size GetConsoleScreenSize(HANDLE outputHandle)
static til::point GetConsoleScreenSize(HANDLE outputHandle)
{
CONSOLE_SCREEN_BUFFER_INFOEX csbiex{};
csbiex.cbSize = sizeof(csbiex);
@@ -34,7 +34,7 @@ static ConnectionState RunConnectionToCompletion(const ITerminalConnection& conn
reader.SetWindowSizeChangedCallback([&]() {
const auto size = GetConsoleScreenSize(outputHandle);
connection.Resize(size.height, size.width);
connection.Resize(size.Y, size.X);
});
while (true)
@@ -98,8 +98,8 @@ int wmain(int /*argc*/, wchar_t** /*argv*/)
AzureConnection azureConn{};
winrt::Windows::Foundation::Collections::ValueSet vs{};
vs.Insert(L"initialRows", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.height)));
vs.Insert(L"initialCols", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.width)));
vs.Insert(L"initialRows", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.Y)));
vs.Insert(L"initialCols", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.X)));
azureConn.Initialize(vs);
const auto state = RunConnectionToCompletion(azureConn, conOut, conIn);

View File

@@ -32,7 +32,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
#pragma warning(push)
#pragma warning(disable : 26490)
// C++/WinRT just loves its void**, nothing we can do here _except_ reinterpret_cast
// C++/WinRT just loves it's void**, nothing we can do here _except_ reinterpret_cast
auto raw = reinterpret_cast<::IInspectable**>(pointer);
#pragma warning(pop)

View File

@@ -86,14 +86,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return _isStateOneOf(ConnectionState::Connected);
}
void _resetConnectionState()
{
{
std::lock_guard<std::mutex> stateLock{ _stateMutex };
_connectionState = ConnectionState::NotConnected;
}
}
private:
std::atomic<ConnectionState> _connectionState{ ConnectionState::NotConnected };
mutable std::mutex _stateMutex;

View File

@@ -2,17 +2,17 @@
// Licensed under the MIT license.
#include "pch.h"
#include "ConptyConnection.h"
#include <conpty-static.h>
#include <winternl.h>
#include "CTerminalHandoff.h"
#include "LibraryResources.h"
#include "../../types/inc/Environment.hpp"
#include "../../types/inc/utils.hpp"
#include "ConptyConnection.g.cpp"
#include "CTerminalHandoff.h"
#include "../../types/inc/utils.hpp"
#include "../../types/inc/Environment.hpp"
#include "LibraryResources.h"
using namespace ::Microsoft::Console;
using namespace std::string_view_literals;
@@ -24,9 +24,13 @@ static constexpr auto _errorFormat = L"{0} ({0:#010x})"sv;
// There is a number of ways that the Conpty connection can be terminated (voluntarily or not):
// 1. The connection is Close()d
// 2. The pseudoconsole or process cannot be spawned during Start()
// 3. The read handle is terminated (when OpenConsole exits)
// 3. The client process exits with a code.
// (Successful (0) or any other code)
// 4. The read handle is terminated.
// (This usually happens when the pseudoconsole host crashes.)
// In each of these termination scenarios, we need to be mindful of tripping the others.
// Close() (1) will cause the automatic triggering of (3).
// Closing the pseudoconsole in response to the client exiting (3) can trigger (4).
// Close() (1) will cause the automatic triggering of (3) and (4).
// In a lot of cases, we use the connection state to stop "flapping."
//
// To figure out where we handle these, search for comments containing "EXIT POINT"
@@ -201,8 +205,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const HANDLE hServerProcess,
const HANDLE hClientProcess,
TERMINAL_STARTUP_INFO startupInfo) :
_rows{ 25 },
_cols{ 80 },
_initialRows{ 25 },
_initialCols{ 80 },
_guid{ Utils::CreateGuid() },
_inPipe{ hIn },
_outPipe{ hOut }
@@ -264,8 +268,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_commandline = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"commandline").try_as<Windows::Foundation::IPropertyValue>(), _commandline);
_startingDirectory = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingDirectory").try_as<Windows::Foundation::IPropertyValue>(), _startingDirectory);
_startingTitle = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingTitle").try_as<Windows::Foundation::IPropertyValue>(), _startingTitle);
_rows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _rows);
_cols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _cols);
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
_guid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"guid").try_as<Windows::Foundation::IPropertyValue>(), _guid);
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
if constexpr (Feature_VtPassthroughMode::IsEnabled())
@@ -303,16 +307,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Start()
try
{
bool usingExistingBuffer = false;
if (_isStateAtOrBeyond(ConnectionState::Closed))
{
_resetConnectionState();
usingExistingBuffer = true;
}
_transitionToState(ConnectionState::Connecting);
const til::size dimensions{ gsl::narrow<til::CoordType>(_cols), gsl::narrow<til::CoordType>(_rows) };
const til::size dimensions{ gsl::narrow<til::CoordType>(_initialCols), gsl::narrow<til::CoordType>(_initialRows) };
// If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received
// handoff from an already-started PTY process.
@@ -320,16 +317,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
DWORD flags = PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE;
// If we're using an existing buffer, we want the new connection
// to reuse the existing cursor. When not setting this flag, the
// PseudoConsole sends a clear screen VT code which our renderer
// interprets into making all the previous lines be outside the
// current viewport.
if (usingExistingBuffer)
{
flags |= PSEUDOCONSOLE_INHERIT_CURSOR;
}
if constexpr (Feature_VtPassthroughMode::IsEnabled())
{
if (_passthroughMode)
@@ -376,8 +363,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}
THROW_IF_FAILED(ConptyReleasePseudoConsole(_hPC.get()));
_startTime = std::chrono::high_resolution_clock::now();
// Create our own output handling thread
@@ -402,6 +387,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
LOG_IF_FAILED(SetThreadDescription(_hOutputThread.get(), L"ConptyConnection Output Thread"));
_clientExitWait.reset(CreateThreadpoolWait(
[](PTP_CALLBACK_INSTANCE /*callbackInstance*/, PVOID context, PTP_WAIT /*wait*/, TP_WAIT_RESULT /*waitResult*/) noexcept {
const auto pInstance = static_cast<ConptyConnection*>(context);
if (pInstance)
{
pInstance->_ClientTerminated();
}
},
this,
nullptr));
SetThreadpoolWait(_clientExitWait.get(), _piClient.hProcess, nullptr);
_transitionToState(ConnectionState::Connected);
}
catch (...)
@@ -442,26 +440,40 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
winrt::hstring exitText{ fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status)) };
_TerminalOutputHandlers(L"\r\n");
_TerminalOutputHandlers(exitText);
_TerminalOutputHandlers(L"\r\n");
_TerminalOutputHandlers(RS_(L"CtrlDToClose"));
_TerminalOutputHandlers(L"\r\n");
}
CATCH_LOG();
}
// Method Description:
// - called when the client application (not necessarily its pty) exits for any reason
void ConptyConnection::_LastConPtyClientDisconnected() noexcept
void ConptyConnection::_ClientTerminated() noexcept
try
{
if (_isStateAtOrBeyond(ConnectionState::Closing))
{
// This termination was expected.
return;
}
// EXIT POINT
DWORD exitCode{ 0 };
GetExitCodeProcess(_piClient.hProcess, &exitCode);
// Signal the closing or failure of the process.
// exitCode might be STILL_ACTIVE if a client has called FreeConsole() and
// thus caused the tab to close, even though the CLI app is still running.
_transitionToState(exitCode == 0 || exitCode == STILL_ACTIVE ? ConnectionState::Closed : ConnectionState::Failed);
// Load bearing. Terminating the pseudoconsole will make the output thread exit unexpectedly,
// so we need to signal entry into the correct closing state before we do that.
_transitionToState(exitCode == 0 ? ConnectionState::Closed : ConnectionState::Failed);
// Close the pseudoconsole and wait for all output to drain.
_hPC.reset();
if (auto localOutputThreadHandle = std::move(_hOutputThread))
{
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(localOutputThreadHandle.get(), INFINITE));
}
_indicateExitWithStatus(exitCode);
_piClient.reset();
}
CATCH_LOG()
@@ -480,11 +492,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Resize(uint32_t rows, uint32_t columns)
{
// Always keep these in case we ever want to disconnect/restart
_rows = rows;
_cols = columns;
if (_isConnected())
// If we haven't started connecting at all, it's still fair to update
// the initial rows and columns before we set things up.
if (!_isStateAtOrBeyond(ConnectionState::Connecting))
{
_initialRows = rows;
_initialCols = columns;
}
// Otherwise, we can really only dispatch a resize if we're already connected.
else if (_isConnected())
{
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) }));
}
@@ -532,46 +548,39 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Close() noexcept
try
{
_transitionToState(ConnectionState::Closing);
// .reset()ing either of these two will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients.
// FYI: The other members of this class are concurrently read by the _hOutputThread
// thread running in the background and so they're not safe to be .reset().
_hPC.reset();
_inPipe.reset();
if (_hOutputThread)
if (_transitionToState(ConnectionState::Closing))
{
// Loop around `CancelSynchronousIo()` just in case the signal to shut down was missed.
// This may happen if we called `CancelSynchronousIo()` while not being stuck
// in `ReadFile()` and if OpenConsole refuses to exit in a timely manner.
for (;;)
{
// ConptyConnection::Close() blocks the UI thread, because `_TerminalOutputHandlers` might indirectly
// reference UI objects like `ControlCore`. CancelSynchronousIo() allows us to have the background
// thread exit as fast as possible by aborting any ongoing writes coming from OpenConsole.
CancelSynchronousIo(_hOutputThread.get());
// EXIT POINT
// _clientExitWait holds a CreateThreadpoolWait() which holds a weak reference to "this".
// This manual reset() ensures we wait for it to be teared down via WaitForThreadpoolWaitCallbacks().
_clientExitWait.reset();
_hPC.reset(); // tear down the pseudoconsole (this is like clicking X on a console window)
// CloseHandle() on pipes blocks until any current WriteFile()/ReadFile() has returned.
// CancelSynchronousIo prevents us from deadlocking ourselves.
// At this point in Close(), _inPipe won't be used anymore since the UI parts are torn down.
// _outPipe is probably still stuck in ReadFile() and might currently be written to.
if (_hOutputThread)
{
CancelSynchronousIo(_hOutputThread.get());
}
_inPipe.reset(); // break the pipes
_outPipe.reset();
if (_hOutputThread)
{
// Waiting for the output thread to exit ensures that all pending _TerminalOutputHandlers()
// calls have returned and won't notify our caller (ControlCore) anymore. This ensures that
// we don't call a destroyed event handler asynchronously from a background thread (GH#13880).
const auto result = WaitForSingleObject(_hOutputThread.get(), 1000);
if (result == WAIT_OBJECT_0)
{
break;
}
LOG_LAST_ERROR();
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_hOutputThread.get(), INFINITE));
_hOutputThread.reset();
}
_transitionToState(ConnectionState::Closed);
}
// Now that the background thread is done, we can safely clean up the other system objects, without
// race conditions, or fear of deadlocking ourselves (e.g. by calling CloseHandle() on _outPipe).
_outPipe.reset();
_hOutputThread.reset();
_piClient.reset();
_transitionToState(ConnectionState::Closed);
}
CATCH_LOG()
@@ -628,19 +637,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
if (readFail) // reading failed (we must check this first, because read will also be 0.)
{
// EXIT POINT
const auto lastError = GetLastError();
if (lastError == ERROR_BROKEN_PIPE)
{
_LastConPtyClientDisconnected();
return S_OK;
}
else
if (lastError != ERROR_BROKEN_PIPE)
{
// EXIT POINT
_indicateExitWithStatus(HRESULT_FROM_WIN32(lastError)); // print a message
_transitionToState(ConnectionState::Failed);
return gsl::narrow_cast<DWORD>(HRESULT_FROM_WIN32(lastError));
}
// else we call convertUTF8ChunkToUTF16 with an empty string_view to convert possible remaining partials to U+FFFD
}
const auto result{ til::u8u16(std::string_view{ _buffer.data(), read }, _u16Str, _u8State) };
@@ -685,11 +690,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
winrt::event_token ConptyConnection::NewConnection(const NewConnectionHandler& handler) { return _newConnectionHandlers.add(handler); };
void ConptyConnection::NewConnection(const winrt::event_token& token) { _newConnectionHandlers.remove(token); };
void ConptyConnection::closePseudoConsoleAsync(HPCON hPC) noexcept
{
::ConptyClosePseudoConsoleTimeout(hPC, 0);
}
HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept
try
{

View File

@@ -6,8 +6,16 @@
#include "ConptyConnection.g.h"
#include "ConnectionStateHolder.h"
#include <conpty-static.h>
#include "ITerminalHandoff.h"
namespace wil
{
// These belong in WIL upstream, so when we reingest the change that has them we'll get rid of ours.
using unique_static_pseudoconsole_handle = wil::unique_any<HPCON, decltype(&::ConptyClosePseudoConsole), ::ConptyClosePseudoConsoleNoWait>;
}
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, ConnectionStateHolder<ConptyConnection>
@@ -57,16 +65,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
private:
static void closePseudoConsoleAsync(HPCON hPC) noexcept;
static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept;
static winrt::hstring _commandlineFromProcess(HANDLE process);
HRESULT _LaunchAttachedClient() noexcept;
void _indicateExitWithStatus(unsigned int status) noexcept;
void _LastConPtyClientDisconnected() noexcept;
void _ClientTerminated() noexcept;
til::CoordType _rows{};
til::CoordType _cols{};
til::CoordType _initialRows{};
til::CoordType _initialCols{};
uint64_t _initialParentHwnd{ 0 };
hstring _commandline{};
hstring _startingDirectory{};
@@ -83,7 +90,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
wil::unique_hfile _outPipe; // The pipe for reading output from
wil::unique_handle _hOutputThread;
wil::unique_process_information _piClient;
wil::unique_any<HPCON, decltype(closePseudoConsoleAsync), closePseudoConsoleAsync> _hPC;
wil::unique_static_pseudoconsole_handle _hPC;
wil::unique_threadpool_wait _clientExitWait;
til::u8state _u8State{};
std::wstring _u16Str{};

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