mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-12 17:21:03 +00:00
Compare commits
69 Commits
dev/miniks
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
918c376fca | ||
|
|
8d8bfca91b | ||
|
|
06427c3965 | ||
|
|
58f7f0e836 | ||
|
|
0c68bd1778 | ||
|
|
4def21a018 | ||
|
|
1e357d0694 | ||
|
|
f474c4bacb | ||
|
|
0a76608530 | ||
|
|
d0c4c29693 | ||
|
|
99f146635d | ||
|
|
c133e2f093 | ||
|
|
56cfeb44b0 | ||
|
|
31f57aec8d | ||
|
|
d12c6dfc1d | ||
|
|
3103371722 | ||
|
|
c9a474ddb0 | ||
|
|
1cde67ac46 | ||
|
|
e633a05890 | ||
|
|
6f5b9fba6c | ||
|
|
86f4ca0f43 | ||
|
|
ac3af4db5c | ||
|
|
73f172a1cc | ||
|
|
0f326920de | ||
|
|
ba25f33d90 | ||
|
|
01a64b1b30 | ||
|
|
0141f68aa8 | ||
|
|
9000a7fd3c | ||
|
|
b11055907e | ||
|
|
4d4c75fa1c | ||
|
|
a96e2e6bd2 | ||
|
|
84d68f082e | ||
|
|
41796c2409 | ||
|
|
9eb77cf0f1 | ||
|
|
9cbb172323 | ||
|
|
6e2f53b025 | ||
|
|
d1afa2af14 | ||
|
|
e52549ace0 | ||
|
|
60f63b09cc | ||
|
|
64c2e856cc | ||
|
|
bfebcf248d | ||
|
|
4d1570f4b4 | ||
|
|
3a48e19b1d | ||
|
|
cb81e61949 | ||
|
|
e3181e76c9 | ||
|
|
0163878d23 | ||
|
|
c8f0a5e5d5 | ||
|
|
65b457113d | ||
|
|
7237fced16 | ||
|
|
bf3d79e41a | ||
|
|
0cfb4637e2 | ||
|
|
4c96fc08a1 | ||
|
|
e658431c11 | ||
|
|
9453aa5ee1 | ||
|
|
0a1ed70153 | ||
|
|
69d0973f14 | ||
|
|
57094b7d98 | ||
|
|
54dc30411c | ||
|
|
5d8c0d0b9b | ||
|
|
65295796f2 | ||
|
|
d08d21626f | ||
|
|
57280d8961 | ||
|
|
60d2c2e26d | ||
|
|
dff1b94016 | ||
|
|
bf24cdd4b0 | ||
|
|
1209fa40c3 | ||
|
|
dd702c769d | ||
|
|
e94e08c303 | ||
|
|
83466a4381 |
@@ -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
|
||||
2
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
2
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
@@ -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 you’re reporting a bug about).
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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)
|
||||
|
||||
8
.github/actions/spelling/allow/allow.txt
vendored
8
.github/actions/spelling/allow/allow.txt
vendored
@@ -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
|
||||
|
||||
1
.github/actions/spelling/allow/microsoft.txt
vendored
1
.github/actions/spelling/allow/microsoft.txt
vendored
@@ -56,7 +56,6 @@ QWORD
|
||||
regedit
|
||||
robocopy
|
||||
SACLs
|
||||
segoe
|
||||
sdkddkver
|
||||
Shobjidl
|
||||
Skype
|
||||
|
||||
5
.github/actions/spelling/allow/names.txt
vendored
5
.github/actions/spelling/allow/names.txt
vendored
@@ -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
|
||||
|
||||
12
.github/actions/spelling/expect/expect.txt
vendored
12
.github/actions/spelling/expect/expect.txt
vendored
@@ -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
|
||||
|
||||
1
.github/workflows/addToProject.yml
vendored
1
.github/workflows/addToProject.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
|
||||
26
.wt.json
Normal file
26
.wt.json
Normal 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."
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
778
OpenConsole.sln
778
OpenConsole.sln
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||

|
||||
|
||||
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 |
@@ -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:
|
||||
|
||||

|
||||
|
||||
_figure 1: PuTTY settings_
|
||||
|
||||

|
||||
|
||||
_figure 2: SecureCRT settings_
|
||||
|
||||

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

|
||||
|
||||
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 |
@@ -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.
|
||||
|
||||
|
||||
@@ -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
2
gitbranch.cmd
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
git branch | D:\dev\private\OpenConsole\bin\x64\Debug\Scratch.exe --prefix "git checkout "
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) };
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
})" };
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)'==''" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
--*/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace TerminalApp
|
||||
|
||||
void Quit();
|
||||
|
||||
void ReloadSettings();
|
||||
void LoadSettings();
|
||||
Windows.UI.Xaml.UIElement GetRoot();
|
||||
|
||||
void SetInboundListener();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>• 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>
|
||||
</data>
|
||||
<data name="FailedToParseSubCommands" xml:space="preserve">
|
||||
<value>• Failed to parse all subcommands of nested command.</value>
|
||||
<value>• 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>• 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>
|
||||
</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>• 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>
|
||||
</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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
|
||||
@@ -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
|
||||
|
||||
//////
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user