Compare commits

..

1 Commits

Author SHA1 Message Date
Leonard Hecker
d6707d2444 Update vsconfig 2022-11-30 23:05:43 +01:00
682 changed files with 15707 additions and 25941 deletions

View File

@@ -1,25 +1,57 @@
---
Language: Cpp
BasedOnStyle: Microsoft
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AllowAllArgumentsOnNextLine: true
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AllowAllConstructorInitializersOnNextLine: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortFunctionsOnASingleLine: All
AllowShortCaseLabelsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: Never
#AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
ColumnLimit: 0
CommentPragmas: "suppress"
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: false
FixNamespaceComments: false
IncludeBlocks: Regroup
IncludeCategories:
@@ -31,13 +63,35 @@ IncludeCategories:
Priority: 2
- Regex: '.*'
Priority: 3
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: "BEGIN_TEST_METHOD_PROPERTIES|BEGIN_MODULE|BEGIN_TEST_CLASS|BEGIN_TEST_METHOD"
MacroBlockEnd: "END_TEST_METHOD_PROPERTIES|END_MODULE|END_TEST_CLASS|END_TEST_METHOD"
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Latest
TabWidth: 4
UseTab: Never

View File

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

View File

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

View File

@@ -34,6 +34,7 @@ DNE
DONTADDTORECENT
DWMSBT
DWMWA
DWMWA
DWORDLONG
endfor
ENDSESSION
@@ -54,8 +55,6 @@ GETMOUSEHOVERTIME
Hashtable
HIGHCONTRASTON
HIGHCONTRASTW
hinternet
HINTERNET
hotkeys
href
hrgn
@@ -159,7 +158,6 @@ rcx
REGCLS
RETURNCMD
rfind
RLO
ROOTOWNER
roundf
RSHIFT
@@ -216,8 +214,6 @@ Viewbox
virtualalloc
wcsstr
wcstoui
WDJ
winhttp
winmain
winsta
winstamin
@@ -225,7 +221,6 @@ wmemcmp
wpc
WSF
wsregex
WWH
wwinmain
xchg
XDocument
@@ -251,4 +246,3 @@ xtree
xutility
YIcon
YMax
zwstring

View File

@@ -9,11 +9,9 @@ appxbundle
appxerror
appxmanifest
ATL
autoexec
backplating
bitmaps
BOMs
COMPUTERNAME
CPLs
cpptools
cppvsdbg
@@ -28,7 +26,6 @@ dotnetfeed
DTDs
DWINRT
enablewttlogging
HOMESHARE
Intelli
IVisual
libucrt
@@ -36,7 +33,6 @@ libucrtd
LKG
LOCKFILE
Lxss
makepri
mfcribbon
microsoft
microsoftonline
@@ -54,19 +50,14 @@ pgo
pgosweep
powerrename
powershell
priconfig
PRIINFO
propkey
pscustomobject
QWORD
regedit
resfiles
robocopy
SACLs
segoe
sdkddkver
Shobjidl
sid
Skype
SRW
sxs
@@ -79,7 +70,6 @@ tdbuildteamid
ucrt
ucrtd
unvirtualized
USERDNSDOMAIN
VCRT
vcruntime
Virtualization

View File

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

View File

@@ -109,7 +109,6 @@
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
^src/tools/lnkd/lnkd\.bat$
^src/tools/pixels/pixels\.bat$
^src/tools/RenderingTests/main.cpp$
^src/tools/texttests/fira\.txt$
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
^src/types/ut_types/UtilsTests.cpp$

View File

@@ -18,7 +18,6 @@ BBBBBBBB
BBBBBCCC
BBBBCCCCC
BBGGRR
efg
EFG
EFGh
QQQQQQQQQQABCDEFGHIJ

View File

@@ -2,7 +2,6 @@ aabbcc
ABANDONFONT
abbcc
ABCDEFGHIJKLMNOPQRSTUVWXY
ABCF
abgr
abi
ABORTIFHUNG
@@ -38,7 +37,6 @@ ansicpg
ANSISYS
ANSISYSRC
ANSISYSSC
answerback
antialiasing
ANull
anycpu
@@ -63,6 +61,7 @@ argb
ARRAYSIZE
ARROWKEYS
asan
ASBRST
ASBSET
ASDF
asdfghjkl
@@ -177,6 +176,7 @@ CConsole
CConversion
CCRT
cdd
CDeclaration
CEdit
CELLSIZE
cfae
@@ -194,6 +194,7 @@ chh
chk
CHT
Cic
CLA
Clcompile
CLE
cleartype
@@ -251,13 +252,11 @@ conattrs
conbufferout
concfg
conclnt
concretizations
conddkrefs
condrv
conechokey
conemu
configurability
confusables
conhost
conime
conimeinfo
@@ -312,6 +311,7 @@ CPLINFO
cplusplus
CPPCORECHECK
cppcorecheckrules
cpprest
cpprestsdk
cppwinrt
CProc
@@ -332,7 +332,7 @@ Cspace
csrmsg
CSRSS
csrutil
CSTYLE
cstyle
CSwitch
CTerminal
CText
@@ -409,26 +409,21 @@ DECAUPSS
DECAWM
DECBKM
DECCARA
DECCIR
DECCKM
DECCKSR
DECCOLM
DECCRA
DECCTR
DECDHL
decdld
DECDMAC
DECDWL
DECEKBD
DECERA
DECFRA
DECID
DECINVM
DECKPAM
DECKPM
DECKPNM
DECLRMM
DECMSR
DECNKM
DECNRCM
DECOM
@@ -438,13 +433,9 @@ DECRARA
DECRC
DECREQTPARM
DECRLM
DECRPM
DECRQCRA
DECRQM
DECRQPSR
DECRQSS
DECRQTSR
DECRSPS
decrst
DECSACE
DECSASD
@@ -462,10 +453,8 @@ DECSLRM
DECSMKR
DECSR
DECSTBM
DECSTGLT
DECSTR
DECSWL
DECTABSR
DECTCEM
DECXCPR
DEFAPP
@@ -485,6 +474,7 @@ defterm
DELAYLOAD
DELETEONRELEASE
Delt
demoable
depersist
deprioritized
deserializers
@@ -541,6 +531,7 @@ DSSCL
DSwap
DTest
DTTERM
DUMMYUNIONNAME
dup'ed
dvi
dwl
@@ -593,6 +584,7 @@ ETW
EUDC
EVENTID
eventing
everytime
evflags
evt
execd
@@ -614,12 +606,8 @@ FACESIZE
FAILIFTHERE
fastlink
fcharset
FDEA
fdw
FECF
FEEF
fesb
FFAF
FFDE
FFrom
fgbg
@@ -816,8 +804,6 @@ hkl
HKLM
hlocal
hlsl
HMB
HMK
hmod
hmodule
hmon
@@ -825,7 +811,6 @@ HORZ
hostable
hostlib
HPA
hpcon
HPCON
hpj
HPR
@@ -907,6 +892,7 @@ INSERTMODE
INTERACTIVITYBASE
INTERCEPTCOPYPASTE
INTERNALNAME
inthread
intsafe
INVALIDARG
INVALIDATERECT
@@ -1125,7 +1111,6 @@ Mip
MMBB
mmcc
MMCPL
MMIX
mmsystem
MNC
MNOPQ
@@ -1264,12 +1249,14 @@ ntm
nto
ntrtl
ntstatus
ntsubauth
NTSYSCALLAPI
nttree
nturtl
ntuser
NTVDM
ntverp
NTWIN
nugetversions
nullability
nullness
@@ -1308,6 +1295,8 @@ opencode
opencon
openconsole
openconsoleproxy
OPENIF
OPENLINK
openps
openvt
ORIGINALFILENAME
@@ -1360,7 +1349,9 @@ pcg
pch
PCIDLIST
PCIS
PCLIENT
PCLONG
PCOBJECT
pcon
PCONSOLE
PCONSOLEENDTASK
@@ -1372,6 +1363,7 @@ pcshell
PCSHORT
PCSR
PCSTR
PCUNICODE
PCWCH
PCWCHAR
PCWSTR
@@ -1420,6 +1412,7 @@ PLOGICAL
pnm
PNMLINK
pntm
PNTSTATUS
POBJECT
Podcast
POINTSLIST
@@ -1437,7 +1430,9 @@ PPEB
ppf
ppguid
ppidl
pplx
PPROC
PPROCESS
ppropvar
ppsi
ppsl
@@ -1501,6 +1496,7 @@ ptrs
ptsz
PTYIn
PUCHAR
PUNICODE
pwch
PWDDMCONSOLECONTEXT
pws
@@ -1562,6 +1558,7 @@ REGISTEROS
REGISTERVDM
regkey
REGSTR
reingest
RELBINPATH
remoting
renamer
@@ -1573,7 +1570,6 @@ replatformed
Replymessage
repositorypath
Requiresx
rerasterize
rescap
Resequence
RESETCONTENT
@@ -1774,7 +1770,6 @@ srv
srvinit
srvpipe
ssa
startdir
STARTF
STARTUPINFO
STARTUPINFOEX
@@ -1823,7 +1818,6 @@ SYSCOMMAND
SYSDEADCHAR
SYSKEYDOWN
SYSKEYUP
SYSLIB
SYSLINK
SYSMENU
sysparams
@@ -1857,7 +1851,6 @@ TDP
TEAMPROJECT
tearoff
Teb
Techo
tellp
teraflop
terminalcore
@@ -1949,7 +1942,6 @@ trx
tsattrs
tsf
tsgr
tsm
TStr
TSTRFORMAT
TSub
@@ -2001,6 +1993,7 @@ unittesting
unittests
unk
unknwn
unmark
UNORM
unparseable
unregistering
@@ -2113,6 +2106,7 @@ WDDMCONSOLECONTEXT
wdm
webpage
websites
websockets
wekyb
wex
wextest
@@ -2189,7 +2183,6 @@ wnd
WNDALLOC
WNDCLASS
WNDCLASSEX
WNDCLASSEXW
WNDCLASSW
Wndproc
WNegative
@@ -2295,7 +2288,6 @@ YOffset
YSubstantial
YVIRTUALSCREEN
YWalk
zabcd
Zabcdefghijklmnopqrstuvwxyz
ZCmd
ZCtrl

View File

@@ -10,4 +10,4 @@
\\tests(?![a-z])
\\thread(?![a-z])
\\tools(?![a-z])
\\types?(?![a-z])
\\types(?![a-z])

View File

@@ -35,7 +35,7 @@ ROY\sG\.\sBIV
# hit-count: 71 file-count: 35
# Compiler flags
(?:^|[\t ,"'`=(])-[D](?=[A-Z]{2,}|[A-Z][a-z])
(?:^|[\t ,"'`=(])-[X](?!aml)(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
(?:^|[\t ,"'`=(])-[X](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# hit-count: 41 file-count: 28
# version suffix <word>v#

View File

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

View File

@@ -1,24 +0,0 @@
name: Publish to Winget
on:
release:
types: [published]
env:
REGEX: 'Microsoft\.WindowsTerminal(?:Preview)?_([\d.]+)_8wekyb3d8bbwe\.msixbundle$'
jobs:
publish:
runs-on: windows-latest # Action can only run on Windows
steps:
- name: Publish Windows Terminal ${{ github.event.release.prerelease && 'Preview' || 'Stable' }}
run: |
$assets = '${{ toJSON(github.event.release.assets) }}' | ConvertFrom-Json
$wingetRelevantAsset = $assets | Where-Object { $_.name -like '*.msixbundle' } | Select-Object -First 1
$regex = [Regex]::New($env:REGEX)
$version = $regex.Match($wingetRelevantAsset.name).Groups[1].Value
$wingetPackage = "Microsoft.WindowsTerminal${{ github.event.release.prerelease && '.Preview' || '' }}"
& curl.exe -JLO https://aka.ms/wingetcreate/latest
& .\wingetcreate.exe update $wingetPackage -s -v $version -u $wingetRelevantAsset.browser_download_url -t "${{ secrets.WINGET_TOKEN }}"

View File

@@ -6,7 +6,7 @@
"C_Cpp.loggingLevel": "None",
"files.associations": {
"xstring": "cpp",
"*.idl": "midl3",
"*.idl": "cpp",
"array": "cpp",
"future": "cpp",
"istream": "cpp",
@@ -106,4 +106,4 @@
"**/packages/**": true,
"**/Generated Files/**": true
}
}
}

View File

@@ -1,35 +1,36 @@
{
"version": "1.0",
"components": [
"Microsoft.Component.MSBuild",
"Microsoft.Net.Component.4.5.TargetingPack",
"Microsoft.Net.Component.4.TargetingPack",
"Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
"Microsoft.NetCore.Component.DevelopmentTools",
"Microsoft.VisualStudio.Component.CoreEditor",
"Microsoft.VisualStudio.Workload.CoreEditor",
"Microsoft.VisualStudio.Workload.Universal",
"Microsoft.VisualStudio.Workload.NativeDesktop",
"Microsoft.VisualStudio.Workload.ManagedDesktop",
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
"Microsoft.VisualStudio.Component.DiagnosticTools",
"Microsoft.VisualStudio.Component.Graphics",
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
"Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites",
"Microsoft.VisualStudio.Component.NuGet",
"Microsoft.VisualStudio.Component.Roslyn.Compiler",
"Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
"Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
"Microsoft.Component.MSBuild",
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
"Microsoft.Net.Component.4.TargetingPack",
"Microsoft.Net.Component.4.5.TargetingPack",
"Microsoft.VisualStudio.Component.DiagnosticTools",
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
"Microsoft.VisualStudio.Component.Windows11SDK.22621",
"Microsoft.VisualStudio.ComponentGroup.UWP.Support",
"Microsoft.VisualStudio.Component.VC.CoreIde",
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
"Microsoft.VisualStudio.Component.Graphics",
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.Component.VC.Tools.ARM64",
"Microsoft.VisualStudio.Component.UWP.VC.ARM64",
"Microsoft.VisualStudio.Component.VC.ASAN",
"Microsoft.VisualStudio.Component.VC.v143.x86.x64",
"Microsoft.VisualStudio.Component.VC.CoreIde",
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
"Microsoft.VisualStudio.Component.VC.Tools.ARM64",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.Component.VC.v143.ARM64",
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
"Microsoft.VisualStudio.Component.VC.v143.x86.x64",
"Microsoft.VisualStudio.Component.Windows11SDK.22621",
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
"Microsoft.VisualStudio.ComponentGroup.UWP.Support",
"Microsoft.VisualStudio.ComponentGroup.UWP.VC.v143",
"Microsoft.VisualStudio.Component.UWP.VC.ARM64"
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
"Microsoft.VisualStudio.Workload.CoreEditor",
"Microsoft.VisualStudio.Workload.ManagedDesktop",
"Microsoft.VisualStudio.Workload.NativeDesktop",
"Microsoft.VisualStudio.Workload.Universal"
]
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,14 @@ Copy-Item "build\helix\runtests.cmd" $payloadDir
Copy-Item "build\helix\InstallTestAppDependencies.ps1" "$payloadDir"
Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"
# Extract the unpackaged distribution of Windows Terminal to the payload directory,
# where it will create a subdirectory named terminal-0.0.1.0
# This is referenced in TerminalApp.cs later as part of the test harness.
& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\WindowsTerminalDev_0.0.1.0_x64.zip" -C "$payloadDir"
# Copy the APPX package from the 'drop' artifact dir and Windows Kits
Copy-Item "$repoDirectory\Artifacts\$ArtifactName\appx\CascadiaPackage_0.0.1.0_$Platform.msix" $payloadDir\CascadiaPackage.zip
Copy-Item "C:\program files (x86)\Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Retail\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" $payloadDir\VCLibs.zip
# Rename it to extension of ZIP because Expand-Archive is real sassy on the build machines
# and refuses to unzip it because of its file extension while on a desktop, it just
# does the job without complaining.
# Extract the APPX package
Expand-Archive -LiteralPath $payloadDir\CascadiaPackage.zip -DestinationPath $payloadDir\appx
Expand-Archive -LiteralPath $payloadDir\VCLibs.zip -DestinationPath $payloadDir\appx -Force

View File

@@ -70,7 +70,7 @@ foreach ($testRun in $testRuns.value)
foreach ($testResult in $testResults.value)
{
$info = ConvertFrom-Json ([System.Web.HttpUtility]::HtmlDecode($testResult.comment))
$info = ConvertFrom-Json $testResult.comment
$helixJobId = $info.HelixJobId
$helixWorkItemName = $info.HelixWorkItemName

View File

@@ -1,72 +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"
}
]
}
}
]
}

View File

@@ -3,6 +3,8 @@
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
<!-- This cannot be included in another project that depends on XAML (as it would be a duplicate package ID) -->
<package id="Microsoft.UI.Xaml" version="2.7.3" targetFramework="native" />
<package id="Microsoft.Debugging.Tools.PdbStr" version="20220617.1556.0" targetFramework="native" />
<package id="Microsoft.Debugging.Tools.SrcTool" version="20220617.1556.0" targetFramework="native" />
</packages>

View File

@@ -56,10 +56,14 @@ parameters:
- x64
- x86
- arm64
- name: buildWindowsVersions
type: object
default:
- Win10
- Win11
variables:
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\MakeAppx.exe'
TerminalInternalPackageVersion: "0.0.8"
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"
# because it truncates the value after the first period.
@@ -82,6 +86,13 @@ variables:
NuGetPackBetaVersion: preview
${{ elseif eq(variables['Build.SourceBranchName'], 'main') }}:
NuGetPackBetaVersion: experimental
# The NuGet packages have to use *somebody's* DLLs. We used to force them to
# use the Win10 build outputs, but if there isn't a Win10 build we should use
# the Win11 one.
${{ if containsValue(parameters.buildWindowsVersions, 'Win10') }}:
TerminalBestVersionForNuGetPackages: Win10
${{ else }}:
TerminalBestVersionForNuGetPackages: Win11
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
resources:
@@ -95,9 +106,11 @@ jobs:
matrix:
${{ each config in parameters.buildConfigurations }}:
${{ each platform in parameters.buildPlatforms }}:
${{ config }}_${{ platform }}:
BuildConfiguration: ${{ config }}
BuildPlatform: ${{ platform }}
${{ each windowsVersion in parameters.buildWindowsVersions }}:
${{ config }}_${{ platform }}_${{ windowsVersion }}:
BuildConfiguration: ${{ config }}
BuildPlatform: ${{ platform }}
TerminalTargetWindowsVersion: ${{ windowsVersion }}
displayName: Build
timeoutInMinutes: 240
cancelTimeoutInMinutes: 1
@@ -171,6 +184,10 @@ jobs:
arguments: -MarkdownNoticePath .\NOTICE.md -OutputPath .\src\cascadia\CascadiaPackage\NOTICE.html
pwsh: true
- ${{ if eq(parameters.buildTerminal, true) }}:
- pwsh: |-
./build/scripts/Patch-ManifestsToWindowsVersion.ps1 -NewWindowsVersion "10.0.22000.0"
displayName: Update manifest target version to Win11 (if necessary)
condition: and(succeeded(), eq(variables['TerminalTargetWindowsVersion'], 'Win11'))
- task: VSBuild@1
displayName: Build solution **\OpenConsole.sln
condition: true
@@ -187,7 +204,7 @@ jobs:
continueOnError: True
inputs:
PathtoPublish: $(Build.SourcesDirectory)\msbuild.binlog
ArtifactName: binlog-$(BuildPlatform)
ArtifactName: binlog-$(BuildPlatform)-$(TerminalTargetWindowsVersion)
- task: PowerShell@2
displayName: Check MSIX for common regressions
inputs:
@@ -236,12 +253,16 @@ jobs:
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
- ${{ if eq(parameters.buildTerminal, true) }}:
- task: CopyFiles@2
displayName: Copy *.msix and symbols to Artifacts
displayName: Copy *.appx/*.msix to Artifacts
inputs:
Contents: >-
**/*.appx
**/*.msix
**/*.appxsym
!**/Microsoft.VCLibs*.appx
TargetFolder: $(Build.ArtifactStagingDirectory)/appx
OverWrite: true
flattenFolders: true
@@ -249,43 +270,11 @@ 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:
PathtoPublish: $(Build.ArtifactStagingDirectory)/appx
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)
- pwsh: |-
$XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $(WindowsTerminalPackagePath) -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/unpackaged"
displayName: Build Unpackaged Distribution
- publish: $(Build.ArtifactStagingDirectory)/unpackaged
artifact: unpackaged-$(BuildPlatform)-$(BuildConfiguration)
displayName: Publish Artifact (unpackaged)
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion)
- ${{ if eq(parameters.buildConPTY, true) }}:
- task: CopyFiles@2
displayName: Copy ConPTY to Artifacts
@@ -303,7 +292,7 @@ jobs:
displayName: Publish Artifact (ConPTY)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/conpty
ArtifactName: conpty-dll-$(BuildPlatform)-$(BuildConfiguration)
ArtifactName: conpty-dll-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion)
- ${{ if eq(parameters.buildWPF, true) }}:
- task: CopyFiles@2
displayName: Copy PublicTerminalCore.dll to Artifacts
@@ -317,7 +306,7 @@ jobs:
displayName: Publish Artifact (PublicTerminalCore)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/wpf
ArtifactName: wpf-dll-$(BuildPlatform)-$(BuildConfiguration)
ArtifactName: wpf-dll-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion)
- task: PublishSymbols@2
displayName: Publish symbols path
@@ -335,6 +324,11 @@ jobs:
- ${{ if eq(parameters.buildTerminal, true) }}:
- job: BundleAndSign
strategy:
matrix:
${{ each windowsVersion in parameters.buildWindowsVersions }}:
${{ windowsVersion }}:
TerminalTargetWindowsVersion: ${{ windowsVersion }}
displayName: Create and sign AppX/MSIX bundles
variables:
${{ if eq(parameters.branding, 'Release') }}:
@@ -356,9 +350,9 @@ jobs:
disableOutputRedirect: true
- ${{ each platform in parameters.buildPlatforms }}:
- task: DownloadBuildArtifacts@0
displayName: Download Artifacts ${{ platform }}
displayName: Download Artifacts ${{ platform }} $(TerminalTargetWindowsVersion)
inputs:
artifactName: appx-${{ platform }}-Release
artifactName: appx-${{ platform }}-Release-$(TerminalTargetWindowsVersion)
# Add 3000 to the major version component, but only for the bundle.
# This is to ensure that it is newer than "2022.xx.yy.zz" or whatever the original bundle versions were before
# we switched to uniform naming.
@@ -368,7 +362,7 @@ jobs:
$Components[0] = ([int]$Components[0] + $VersionEpoch)
$BundleVersion = $Components -Join "."
New-Item -Type Directory "$(System.ArtifactsDirectory)\bundle"
.\build\scripts\Create-AppxBundle.ps1 -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
.\build\scripts\Create-AppxBundle.ps1 -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(TerminalTargetWindowsVersion)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
displayName: Create WindowsTerminal*.msixbundle
- task: EsrpCodeSigning@1
displayName: Submit *.msixbundle to ESRP for code signing
@@ -409,7 +403,7 @@ jobs:
displayName: 'Publish Artifact: appxbundle-signed'
inputs:
PathtoPublish: $(System.ArtifactsDirectory)\bundle
ArtifactName: appxbundle-signed
ArtifactName: appxbundle-signed-$(TerminalTargetWindowsVersion)
- ${{ if eq(parameters.buildConPTY, true) }}:
- job: PackageAndSignConPTY
@@ -434,7 +428,7 @@ jobs:
- task: DownloadBuildArtifacts@0
displayName: Download ${{ platform }} ConPTY binaries
inputs:
artifactName: conpty-dll-${{ platform }}-$(BuildConfiguration)
artifactName: conpty-dll-${{ platform }}-$(BuildConfiguration)-$(TerminalBestVersionForNuGetPackages)
downloadPath: bin\${{ platform }}\$(BuildConfiguration)\
extractTars: false
- task: PowerShell@2
@@ -525,7 +519,7 @@ jobs:
- task: DownloadBuildArtifacts@0
displayName: Download ${{ platform }} PublicTerminalCore
inputs:
artifactName: wpf-dll-${{ platform }}-$(BuildConfiguration)
artifactName: wpf-dll-${{ platform }}-$(BuildConfiguration)-$(TerminalBestVersionForNuGetPackages)
itemPattern: '**/*.dll'
downloadPath: bin\${{ platform }}\$(BuildConfiguration)\
extractTars: false
@@ -623,10 +617,11 @@ jobs:
# Download the appx-PLATFORM-CONFIG-VERSION artifact for every platform/version combo
- ${{ each platform in parameters.buildPlatforms }}:
- task: DownloadBuildArtifacts@0
displayName: Download Symbols ${{ platform }}
inputs:
artifactName: appx-${{ platform }}-Release
- ${{ each windowsVersion in parameters.buildWindowsVersions }}:
- task: DownloadBuildArtifacts@0
displayName: Download Symbols ${{ platform }} ${{ windowsVersion }}
inputs:
artifactName: appx-${{ platform }}-Release-${{ windowsVersion }}
# It seems easier to do this -- download every appxsym -- then enumerate all the PDBs in the build directory for the
# public symbol push. Otherwise, we would have to list all of the PDB files one by one.
@@ -686,7 +681,7 @@ jobs:
- task: DownloadBuildArtifacts@0
displayName: Download Build Artifacts
inputs:
artifactName: appxbundle-signed
artifactName: appxbundle-signed-Win11
extractTars: false
- task: PowerShell@2
displayName: Rename and stage packages for vpack
@@ -695,7 +690,7 @@ jobs:
script: >-
# Rename to known/fixed name for Windows build system
Get-ChildItem Microsoft.WindowsTerminal_*.msixbundle | Rename-Item -NewName { 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle' }
Get-ChildItem Microsoft.WindowsTerminal_Win11_*.msixbundle | Rename-Item -NewName { 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle' }
# Create vpack directory and place item inside
@@ -703,13 +698,13 @@ jobs:
mkdir WindowsTerminal.app
mv Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle .\WindowsTerminal.app\
workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed
workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed-Win11
- task: PkgESVPack@12
displayName: 'Package ES - VPack'
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
sourceDirectory: $(System.ArtifactsDirectory)\appxbundle-signed\WindowsTerminal.app
sourceDirectory: $(System.ArtifactsDirectory)\appxbundle-signed-Win11\WindowsTerminal.app
description: VPack for the Windows Terminal Application
pushPkgName: WindowsTerminal.app
owner: conhost

View File

@@ -144,7 +144,7 @@ jobs:
inputs:
TargetPattern: guardianGlob
# See https://aka.ms/gdn-globs for how to do match patterns
AnalyzeTargetGlob: $(Build.SourcesDirectory)\bin\**\*.dll;$(Build.SourcesDirectory)\bin\**\*.exe;-:file|**\Microsoft.UI.Xaml.dll;-:file|**\Microsoft.Toolkit.Win32.UI.XamlHost.dll;-:file|**\vcruntime*.dll;-:file|**\vcomp*.dll;-:file|**\vccorlib*.dll;-:file|**\vcamp*.dll;-:file|**\msvcp*.dll;-:file|**\concrt*.dll;-:file|**\TerminalThemeHelpers*.dll
AnalyzeTargetGlob: $(Build.SourcesDirectory)\bin\**\*.dll;$(Build.SourcesDirectory)\bin\**\*.exe;-:file|**\Microsoft.UI.Xaml.dll;-:file|**\Microsoft.Toolkit.Win32.UI.XamlHost.dll;-:file|**\vcruntime*.dll;-:file|**\vcomp*.dll;-:file|**\vccorlib*.dll;-:file|**\vcamp*.dll;-:file|**\msvcp*.dll;-:file|**\concrt*.dll;-:file|**\TerminalThemeHelpers*.dll;-:file|**\cpprest*.dll
continueOnError: true
# Set XES_SERIALPOSTBUILDREADY to run Security and Compliance task once per build

View File

@@ -64,24 +64,17 @@ steps:
Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}"
- task: CopyFiles@2
displayName: 'Copy *.msix to Artifacts'
displayName: 'Copy *.appx/*.msix to Artifacts (Non-PR builds only)'
inputs:
Contents: |
**/*.appx
**/*.msix
**/*.appxsym
!**/Microsoft.VCLibs*.appx
TargetFolder: '$(Build.ArtifactStagingDirectory)/appx'
OverWrite: true
flattenFolders: true
- pwsh: |-
$TerminalMsixPath = (Get-Item "$(Build.ArtifactStagingDirectory)\appx\Cascadia*.msix").FullName
$XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $TerminalMsixPath -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/unpackaged"
displayName: Build Unpackaged Distribution
- publish: $(Build.ArtifactStagingDirectory)/unpackaged
artifact: unpackaged-$(BuildPlatform)-$(BuildConfiguration)
displayName: Publish Artifact (unpackaged)
condition: succeeded()
- task: CopyFiles@2
displayName: 'Copy outputs needed for test runs to Artifacts'

View File

@@ -14,8 +14,8 @@ parameters:
platform: ''
# if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline:
useBuildOutputFromPipeline: $(System.DefinitionId)
openHelixTargetQueues: 'windows.11.amd64.client.open.reunion'
closedHelixTargetQueues: 'windows.11.amd64.client.reunion'
openHelixTargetQueues: 'windows.10.amd64.client21h1.open.xaml'
closedHelixTargetQueues: 'windows.10.amd64.client21h1.xaml'
jobs:
- job: ${{ parameters.name }}

View File

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

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
This file contains targets that override behavior in Microsoft.UI.Xaml and
related packages.
For example: All XAML needs is a reference to WebView2; it does not need the
DLL and it does not need for us to copy the WinMD into the output folder. It
also doesn't require the WebView2 loader since we're not actually using
WebView2. Therefore, we can get away with *not including the WebView2
package* and only adding a reference to its winmd.
-->
<ItemGroup>
<Reference Include="$(WebView2PackageRoot)\lib\Microsoft.Web.WebView2.Core.winmd" />
</ItemGroup>
</Project>

View File

@@ -1,78 +0,0 @@
Param(
[Parameter(Mandatory,
HelpMessage="List of PRI files or XML dumps (detailed only) to merge")]
[string[]]
$Path,
[Parameter(Mandatory,
HelpMessage="Output Path")]
[string]
$OutputPath,
[Parameter(HelpMessage="Name of index in output file; defaults to 'Application'")]
[string]
$IndexName = "Application",
[Parameter(HelpMessage="Path to makepri.exe")]
[ValidateScript({Test-Path $_ -Type Leaf})]
[string]
$MakePriPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakePri.exe"
)
$ErrorActionPreference = 'Stop'
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp"
New-Item -ItemType Directory -Path $tempDir | Out-Null
$priConfig = Join-Path $tempDir "priconfig.xml"
$priListFile = Join-Path $tempDir "pri.resfiles"
$dumpListFile = Join-Path $tempDir "dump.resfiles"
@"
<?xml version="1.0" encoding="utf-8"?>
<resources targetOsVersion="10.0.0" majorVersion="1">
<index root="\" startIndexAt="dump.resfiles">
<default>
<qualifier name="Language" value="en-US" />
<qualifier name="Contrast" value="standard" />
<qualifier name="Scale" value="200" />
<qualifier name="HomeRegion" value="001" />
<qualifier name="TargetSize" value="256" />
<qualifier name="LayoutDirection" value="LTR" />
<qualifier name="DXFeatureLevel" value="DX9" />
<qualifier name="Configuration" value="" />
<qualifier name="AlternateForm" value="" />
<qualifier name="Platform" value="UAP" />
</default>
<indexer-config type="PRIINFO" />
<indexer-config type="RESFILES" qualifierDelimiter="." />
</index>
<index root="\" startIndexAt="pri.resfiles">
<default>
<qualifier name="Language" value="en-US" />
<qualifier name="Contrast" value="standard" />
<qualifier name="Scale" value="200" />
<qualifier name="HomeRegion" value="001" />
<qualifier name="TargetSize" value="256" />
<qualifier name="LayoutDirection" value="LTR" />
<qualifier name="DXFeatureLevel" value="DX9" />
<qualifier name="Configuration" value="" />
<qualifier name="AlternateForm" value="" />
<qualifier name="Platform" value="UAP" />
</default>
<indexer-config type="PRI" />
<indexer-config type="RESFILES" qualifierDelimiter="." />
</index>
</resources>
"@ | Out-File -Encoding:utf8NoBOM $priConfig
$Path | Where { $_ -Like "*.pri" } | ForEach-Object {
Get-Item $_ | Select -Expand FullName
} | Out-File -Encoding:utf8NoBOM $priListFile
$Path | Where { $_ -Like "*.xml" } | ForEach-Object {
Get-Item $_ | Select -Expand FullName
} | Out-File -Encoding:utf8NoBOM $dumpListFile
& $MakePriPath new /pr $tempDir /cf $priConfig /o /in $IndexName /of $OutputPath
Remove-Item -Recurse -Force $tempDir

View File

@@ -1,47 +0,0 @@
Param(
[Parameter(Mandatory,
HelpMessage="Root directory of extracted Terminal AppX")]
[string[]]
$TerminalRoot,
[Parameter(Mandatory,
HelpMessage="Root directory of extracted Xaml AppX")]
[string[]]
$XamlRoot,
[Parameter(Mandatory,
HelpMessage="Output Path")]
[string]
$OutputPath,
[Parameter(HelpMessage="Path to makepri.exe")]
[ValidateScript({Test-Path $_ -Type Leaf})]
[string]
$MakePriPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakePri.exe"
)
$ErrorActionPreference = 'Stop'
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp"
New-Item -ItemType Directory -Path $tempDir | Out-Null
$terminalDump = Join-Path $tempDir "terminal.pri.xml"
& $MakePriPath dump /if (Join-Path $TerminalRoot "resources.pri") /of $terminalDump /dt detailed
Write-Verbose "Removing Microsoft.UI.Xaml node from Terminal to prevent a collision with XAML"
$terminalXMLDocument = [xml](Get-Content $terminalDump)
$resourceMap = $terminalXMLDocument.PriInfo.ResourceMap
$fileSubtree = $resourceMap.ResourceMapSubtree | Where-Object { $_.Name -eq "Files" }
$subtrees = $fileSubtree.ResourceMapSubtree
$xamlSubtreeChild = ($subtrees | Where-Object { $_.Name -eq "Microsoft.UI.Xaml" })
if ($Null -Ne $xamlSubtreeChild) {
$null = $fileSubtree.RemoveChild($xamlSubtreeChild)
$terminalXMLDocument.Save($terminalDump)
}
$indexName = $terminalXMLDocument.PriInfo.ResourceMap.name
& (Join-Path $PSScriptRoot "Merge-PriFiles.ps1") -Path $terminalDump, (Join-Path $XamlRoot "resources.pri") -IndexName $indexName -OutputPath $OutputPath -MakePriPath $MakePriPath
Remove-Item -Recurse -Force $tempDir

View File

@@ -1,140 +0,0 @@
[CmdletBinding(DefaultParameterSetName = 'AppX')]
Param(
[Parameter(Mandatory, HelpMessage="Path to Terminal AppX", ParameterSetName = 'AppX')]
[ValidateScript({Test-Path $_ -Type Leaf})]
[string]
$TerminalAppX,
[Parameter(Mandatory, HelpMessage="Path to Terminal Layout Deployment", ParameterSetName='Layout')]
[ValidateScript({Test-Path $_ -Type Container})]
[string]
$TerminalLayout,
[Parameter(Mandatory, HelpMessage="Path to Xaml AppX", ParameterSetName='AppX')]
[Parameter(Mandatory, HelpMessage="Path to Xaml AppX", ParameterSetName='Layout')]
[ValidateScript({Test-Path $_ -Type Leaf})]
[string]
$XamlAppX,
[Parameter(HelpMessage="Output Directory", ParameterSetName='AppX')]
[Parameter(HelpMessage="Output Directory", ParameterSetName='Layout')]
[string]
$Destination = ".",
[Parameter(HelpMessage="Path to makeappx.exe", ParameterSetName='AppX')]
[Parameter(HelpMessage="Path to makeappx.exe", ParameterSetName='Layout')]
[ValidateScript({Test-Path $_ -Type Leaf})]
[string]
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakeAppx.exe"
)
$filesToRemove = @("*.xml", "*.winmd", "Appx*", "Images/*Tile*", "Images/*Logo*") # Remove from Terminal
$filesToKeep = @("Microsoft.Terminal.Remoting.winmd") # ... except for these
$filesToCopyFromXaml = @("Microsoft.UI.Xaml.dll", "Microsoft.UI.Xaml") # We don't need the .winmd
$ErrorActionPreference = 'Stop'
If ($null -Eq (Get-Item $MakeAppxPath -EA:SilentlyContinue)) {
Write-Error "Could not find MakeAppx.exe at `"$MakeAppxPath`".`nMake sure that -MakeAppxPath points to a valid SDK."
Exit 1
}
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp"
New-Item -ItemType Directory -Path $tempDir | Out-Null
$XamlAppX = Get-Item $XamlAppX | Select-Object -Expand FullName
########
# Reading the AppX Manifest for preliminary info
########
If ($TerminalAppX) {
$appxManifestPath = Join-Path $tempDir AppxManifest.xml
& tar.exe -x -f "$TerminalAppX" -C $tempDir AppxManifest.xml
} ElseIf($TerminalLayout) {
$appxManifestPath = Join-Path $TerminalLayout AppxManifest.xml
}
$manifest = [xml](Get-Content $appxManifestPath)
$pfn = $manifest.Package.Identity.Name
$version = $manifest.Package.Identity.Version
$architecture = $manifest.Package.Identity.ProcessorArchitecture
$distributionName = "{0}_{1}_{2}" -f ($pfn, $version, $architecture)
$terminalDir = "terminal-{0}" -f ($version)
########
# Unpacking Terminal and XAML
########
$terminalAppPath = Join-Path $tempdir $terminalDir
If ($TerminalAppX) {
$TerminalAppX = Get-Item $TerminalAppX | Select-Object -Expand FullName
New-Item -ItemType Directory -Path $terminalAppPath | Out-Null
& $MakeAppxPath unpack /p $TerminalAppX /d $terminalAppPath /o | Out-Null
If ($LASTEXITCODE -Ne 0) {
Throw "Unpacking $TerminalAppX failed"
}
} ElseIf ($TerminalLayout) {
Copy-Item -Recurse -Path $TerminalLayout -Destination $terminalAppPath
}
$xamlAppPath = Join-Path $tempdir "xaml"
New-Item -ItemType Directory -Path $xamlAppPath | Out-Null
& $MakeAppxPath unpack /p $XamlAppX /d $xamlAppPath /o | Out-Null
If ($LASTEXITCODE -Ne 0) {
Throw "Unpacking $XamlAppX failed"
}
########
# Some sanity checking
########
$xamlManifest = [xml](Get-Content (Join-Path $xamlAppPath "AppxManifest.xml"))
If ($xamlManifest.Package.Identity.Name -NotLike "Microsoft.UI.Xaml*") {
Throw "$XamlAppX is not a XAML package (instead, it looks like $($xamlManifest.Package.Identity.Name))"
}
If ($xamlManifest.Package.Identity.ProcessorArchitecture -Ne $architecture) {
Throw "$XamlAppX is not built for $architecture (instead, it is built for $($xamlManifest.Package.Identity.ProcessorArchitecture))"
}
########
# Preparation of source files
########
$itemsToRemove = $filesToRemove | ForEach-Object {
Get-Item (Join-Path $terminalAppPath $_) -EA:SilentlyContinue | Where-Object {
$filesToKeep -NotContains $_.Name
}
} | Sort-Object FullName -Unique
$itemsToRemove | Remove-Item -Recurse
$filesToCopyFromXaml | ForEach-Object {
Get-Item (Join-Path $xamlAppPath $_)
} | Copy-Item -Recurse -Destination $terminalAppPath
########
# Resource Management
########
$finalTerminalPriFile = Join-Path $terminalAppPath "resources.pri"
& (Join-Path $PSScriptRoot "Merge-TerminalAndXamlResources.ps1") `
-TerminalRoot $terminalAppPath `
-XamlRoot $xamlAppPath `
-OutputPath $finalTerminalPriFile `
-Verbose:$Verbose | Out-Host
########
# Packaging
########
If ($PSCmdlet.ParameterSetName -Eq "AppX") {
# We only produce a ZIP when we're combining two AppX directories.
New-Item -ItemType Directory -Path $Destination -ErrorAction:SilentlyContinue | Out-Null
$outputZip = (Join-Path $Destination ("{0}.zip" -f ($distributionName)))
& tar -c --format=zip -f $outputZip -C $tempDir $terminalDir
Remove-Item -Recurse -Force $tempDir -EA:SilentlyContinue
Get-Item $outputZip
} ElseIf ($PSCmdlet.ParameterSetName -Eq "Layout") {
Get-Item $terminalAppPath
}

View File

@@ -0,0 +1,14 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Param(
[string]$NewWindowsVersion = "10.0.22000.0"
)
Get-ChildItem src/cascadia/CascadiaPackage -Recurse -Filter *.appxmanifest | ForEach-Object {
$xml = [xml](Get-Content $_.FullName)
$xml.Package.Dependencies.TargetDeviceFamily | Where-Object Name -Like "Windows*" | ForEach-Object {
$_.MinVersion = $NewWindowsVersion
}
$xml.Save($_.FullName)
}

View File

@@ -70,24 +70,23 @@ Try {
$dependencies = $Manifest.Package.Dependencies.PackageDependency.Name
$depsHasVclibsDesktop = ("Microsoft.VCLibs.140.00.UWPDesktop" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug.UWPDesktop" -in $dependencies)
$depsHasVclibsAppX = ("Microsoft.VCLibs.140.00" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug" -in $dependencies)
$depsHasVcLibsAppX = ("Microsoft.VCLibs.140.00" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug" -in $dependencies)
$filesHasVclibsDesktop = ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140.dll" -EA:Ignore)) -or ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140d.dll" -EA:Ignore))
$filesHasVclibsAppX = ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140_app.dll" -EA:Ignore)) -or ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140d_app.dll" -EA:Ignore))
If ($filesHasVclibsDesktop) {
Throw "Package contains the desktop VCLibs"
If ($depsHasVclibsDesktop -Eq $filesHasVclibsDesktop) {
$eitherBoth = if ($depsHasVclibsDesktop) { "both" } else { "neither" }
$neitherNor = if ($depsHasVclibsDesktop) { "and" } else { "nor" }
Throw "Package has $eitherBoth Dependency $neitherNor Integrated Desktop VCLibs"
}
If ($depsHasVclibsDesktop) {
Throw "Package has a dependency on the desktop VCLibs"
}
If ($filesHasVclibsAppX) {
Throw "Package contains the AppX VCLibs"
}
If ($depsHasVclibsAppX) {
Throw "Package has a dependency on the AppX VCLibs"
If ($depsHasVclibsAppx -Eq $filesHasVclibsAppx) {
if ($depsHasVclibsAppx) {
# We've shipped like this forever, so downgrade to warning.
Write-Warning "Package has both Dependency and Integrated AppX VCLibs"
} else {
Throw "Package has neither Dependency nor Integrated AppX VCLibs"
}
}
### Check that we have an App.xbf (which is a proxy for our resources having been merged)
@@ -97,6 +96,11 @@ Try {
Throw "Failed to find App.xbf (TerminalApp project) in resources.pri"
}
If (($null -eq (Get-Item "$AppxPackageRootPath\cpprest142_2_10.dll" -EA:Ignore)) -And
($null -eq (Get-Item "$AppxPackageRootPath\cpprest142_2_10d.dll" -EA:Ignore))) {
Throw "Failed to find cpprest142_2_10.dll -- check the WAP packaging project"
}
If (($null -eq (Get-Item "$AppxPackageRootPath\wtd.exe" -EA:Ignore)) -And
($null -eq (Get-Item "$AppxPackageRootPath\wt.exe" -EA:Ignore))) {
Throw "Failed to find wt.exe/wtd.exe -- check the WAP packaging project"

View File

@@ -10,4 +10,22 @@
<OpenConsoleDir>$(MSBuildThisFileDirectory)</OpenConsoleDir>
</PropertyGroup>
<PropertyGroup>
<!--
For the Windows 10 build, we're targeting the prerelease version of Microsoft.UI.Xaml.
This version emits every XAML DLL directly into our package.
This is a workaround for us not having deliverable MSFT-21242953 on this version of Windows.
This version should be tracked in all project packages.config files for projects that depend on Xaml.
-->
<TerminalMUXVersion>2.7.3-prerelease.220816001</TerminalMUXVersion>
<!--
For the Windows 11-specific build, we're targeting the public version of Microsoft.UI.Xaml.
This version emits a package dependency instead of embedding the dependency in our own package.
This version should be tracked in build/packages.config.
-->
<TerminalMUXVersion Condition="'$(TerminalTargetWindowsVersion)'=='Win11'">2.7.3</TerminalMUXVersion>
</PropertyGroup>
</Project>

View File

@@ -2,10 +2,22 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file is read by XES, which we use in our Release builds. -->
<PropertyGroup Label="Version">
<!--
The Windows 11 build is going to have the same package name, so it *must* have a different version.
The easiest way for us to do this is to add 1 to the revision field.
In short, for a given Terminal build 1.11, we will emit two different versions (assume this is build
4 on day 23 of the year):
- 1.11.234.0 for Windows 10
- 1.11.235.0 for Windows 11
This presents a potential for conflicts if we want to ship two builds produced back to back on the
same day... which is terribly unlikely.
-->
<VersionBuildRevision Condition="'$(TerminalTargetWindowsVersion)'=='Win11' and '$(VersionBuildRevision)'!=''">$([MSBuild]::Add($(VersionBuildRevision), 1))</VersionBuildRevision>
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2023</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2022</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>18</VersionMinor>
<VersionMinor>17</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -2,13 +2,15 @@
<!-- 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.230207.1" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
<package id="vcpkg-cpprestsdk" version="2.10.14" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.6.220404001" targetFramework="native" />
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
<package id="Microsoft.UI.Xaml" version="2.8.3" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.7.3-prerelease.220816001" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220201.1" targetFramework="native" developmentDependency="true" />
<!-- Managed packages -->

View File

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

View File

@@ -24,13 +24,6 @@
"pattern": "^(-?\\d+)?(,\\s?(-?\\d+)?)?$",
"type": "string"
},
"CSSLengthPercentage": {
"pattern": "^[+-]?\\d+(?:\\.\\d+)?(?:%|ch|pt|px)?$",
"type": [
"string",
"null"
]
},
"DynamicProfileSource": {
"enum": [
"Windows.Terminal.Wsl",
@@ -89,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",
@@ -247,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": {
@@ -321,14 +292,6 @@
}
},
"additionalProperties": false
},
"cellWidth": {
"$ref": "#/$defs/CSSLengthPercentage",
"description": "Override the width of the terminal's cells. The override works similar to CSS' letter-spacing. It defaults to the natural glyph advance width of the primary font rounded to the nearest pixel."
},
"cellHeight": {
"$ref": "#/$defs/CSSLengthPercentage",
"description": "Override the height of the terminal's cells. The override works similar to CSS' line-height. Defaults to the sum of the natural glyph ascend, descend and line-gap of the primary font rounded to the nearest pixel. The default is usually quite close to setting this to 1.2."
}
},
"type": "object"
@@ -576,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": [
{
@@ -1737,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": {
@@ -2092,11 +1762,6 @@
"description": "When set to true, the terminal will focus the pane on mouse hover.",
"type": "boolean"
},
"compatibility.isolatedMode": {
"default": false,
"description": "When set to true, Terminal windows will not be able to interact with each other (including global hotkeys, tab drag/drop, running commandlines in existing windows, etc.). This is a compatibility escape hatch for users who are running into certain windowing issues.",
"type": "boolean"
},
"copyFormatting": {
"default": true,
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
@@ -2169,11 +1834,6 @@
"description": "When set to true, the background image for the currently focused profile is expanded to encompass the entire window, beneath other panes.",
"type": "boolean"
},
"compatibility.reloadEnvironmentVariables": {
"default": true,
"description": "When set to true, when opening a new tab or pane it will get reloaded environment variables.",
"type": "boolean"
},
"initialCols": {
"default": 120,
"description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",
@@ -2264,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,
@@ -2522,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.",
@@ -2574,13 +2215,6 @@
"default": false,
"description": "When true, this profile should always open in an elevated context. If the window isn't running as an Administrator, then a new elevated window will be created."
},
"environment": {
"description": "Key-value pairs representing environment variables to set. Environment variable names are not case sensitive. You can reference existing environment variable names by enclosing them in literal percent characters (e.g. %PATH%).",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"experimental.autoMarkPrompts": {
"default": false,
"description": "When set to true, prompts will automatically be marked.",

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

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

View File

@@ -1,90 +0,0 @@
---
author: Dustin L. Howett @DHowett
created on: 2023-03-22
last updated: 2023-03-22
issue id: none
---
# Windows Terminal "Portable" Mode
## Abstract
Since we are planning on officially supporting unpackaged execution, I propose a special mode where Terminal stores its
settings in a `settings` folder next to `WindowsTerminal.exe`.
## Inspiration
- [PortableApps](https://portableapps.com)
- "Embeddable" Python, which relies on the deployment of a specific file to the Python root
## Solution Design
- _If running without package identity,_ `CascadiaSettings` will look for the presence of a file called `.portable` next
to `Microsoft.Terminal.Settings.Model.dll`.
- If that file is present, it will change the settings and state paths to be rooted in a subfolder named `settings` next
to `Microsoft.Terminal.Settings.Model.dll`.
Right now, _the only thing_ that makes Terminal not work in a "portable" manner is that it saves settings to
`%LOCALAPPDATA%`.
## UI/UX Design
_No UI/UX impact is expected._
## Capabilities
- Distributors could ship a self-contained and preconfigured Terminal installation.
- Users could archive fully-working preconfigured versions of Terminal.
- Developers (such as those on the team) could easily test multiple versions of Terminal without worrying about global
settings pollution.
### Accessibility
_No change is expected._
### Security
_No change is expected._
### Reliability
More code always bears a risk.
### Compatibility
This is a net new feature, and it does not break any existing features. A distributor (or a user) can opt in (or out) by
adding (or removing) the `.portable` file.
The following features may be impacted.
- **Dynamic Profiles** and **Fragment Extensions**
- _No impact expected._ Dynamic profiles will still be generated. If a portable installation is moved to a machine without the dynamic profile source, that profile will disappear.
- `firstWindowPreference` and `state.json`
- _No impact expected._
- State is stored next to settings, even for portable installations.
- If a dynamic profile was saved in `state` and has been removed, Terminal will proceed as in non-portable mode.
- Moving an install from Windows 10 to Windows 11 and back
- _No impact expected._
- "Machine-specific" settings, like those about rendering and repainting
- _No impact expected._
- Terminal does not distinguish settings that are specific to a machine. These settings will move along with the portable install.
- The shell extension
- _No impact expected._
- The shell extension will not be registered with Windows.
- If we choose to register the shell extension, it is already prepared for running a version of WT from the same directory. Registering the portable shell extension will make it launch portable Terminal.
### Performance, Power, and Efficiency
_No change is expected._
## Potential Issues
- User confusion around where settings are stored.
## Future considerations
- In the future, perhaps `.portable` could itself contain a directory path into which we would store settings.
- We could consider adding an indicator in the Settings UI.
- Because we are using the module path of the Settings Model DLL, a future unpackaged version of the shell extension
that supports profile loading would read the right settings file (assuming it used the settings model.)
- If we choose to store the shell extension cache in the registry, we would need to avoid doing so in portable mode.

View File

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

View File

@@ -6,14 +6,14 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
// Resolution of the shaderTexture
float2 Resolution;
// Background color as rgba
float4 Background;
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
// Resolution of the shaderTexture
float2 Resolution;
// Background color as rgba
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
@@ -29,19 +29,38 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
// effect, read the colors offset on the left, right, top, bottom of this
// fragment, as well as on the corners of this fragment.
//
// You could get away with fewer samples, but the resulting outlines will be
// blurrier.
//left, right, top, bottom:
float4 leftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, 0.0)/Resolution.y);
float4 rightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, 0.0)/Resolution.y);
float4 topColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 0.0, 1.0)/Resolution.y);
float4 bottomColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 0.0, -1.0)/Resolution.y);
// Corners
float4 topLeftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, 1.0)/Resolution.y);
float4 topRightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, 1.0)/Resolution.y);
float4 bottomLeftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, -1.0)/Resolution.y);
float4 bottomRightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, -1.0)/Resolution.y);
// Now, if any of those adjacent cells has text in it, then the *color vec4
// will have a non-zero .w (which is used for alpha). Use that alpha value
// to add some black to the current fragment.
//
// This will result in only coloring fragments adjacent to text, but leaving
// background images (for example) untouched.
float3 outlineColor = float3(0, 0, 0);
float4 result = color;
result = result + float4(outlineColor, leftColor.w);
result = result + float4(outlineColor, rightColor.w);
result = result + float4(outlineColor, topColor.w);
result = result + float4(outlineColor, bottomColor.w);
for (int dy = -2; dy <= 2; dy += 2) {
for (int dx = -2; dx <= 2; dx += 2) {
float4 neighbor = shaderTexture.Sample(samplerState, tex, int2(dx, dy));
color.a += neighbor.a;
}
}
return color;
result = result + float4(outlineColor, topLeftColor.w);
result = result + float4(outlineColor, topRightColor.w);
result = result + float4(outlineColor, bottomLeftColor.w);
result = result + float4(outlineColor, bottomRightColor.w);
return result;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
@@ -127,16 +128,6 @@
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="$(WindowsSDK_MetadataPathVersioned)\Windows.UI.Xaml.Hosting.HostingContract\*\*.winmd">
<WinMDFile>true</WinMDFile>
<CopyLocal>false</CopyLocal>
<ReferenceGrouping>$(TargetPlatformMoniker)</ReferenceGrouping>
<ReferenceGroupingDisplayName>$(TargetPlatformDisplayName)</ReferenceGroupingDisplayName>
<ResolvedFrom>CppWinRTImplicitlyExpandTargetPlatform</ResolvedFrom>
<IsSystemReference>True</IsSystemReference>
</Reference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>

View File

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

View File

@@ -2,5 +2,5 @@
<packages>
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.7.3" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
</packages>

View File

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

View File

@@ -17,6 +17,8 @@
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
<TerminalVCRTForwarders>true</TerminalVCRTForwarders>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup>
@@ -88,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>
@@ -195,6 +197,26 @@
<!-- **END VC LIBS HACK** -->
<!-- **BEGIN TERMINAL CONNECTION HACK** -->
<!-- This is the same as the above VC libs hack, but for TerminalConnection.
TerminalConnection depends on cpprest*.dll, and if we don't include it in
the packaging output, we'll crash as soon as we try to load
TerminalConnection.dll.
The Sample sln needs to do this manually - the real exe has a
ProjectReference to TerminalConnection.vcxproj and can figure this out on
its own. -->
<ItemGroup>
<_TerminalConnectionDlls Include="$(OpenConsoleCommonOutDir)\TerminalConnection\*.dll" />
<PackagingOutputs Include="@(_TerminalConnectionDlls)">
<ProjectName>$(ProjectName)</ProjectName>
<OutputGroup>BuiltProjectOutputGroup</OutputGroup>
<TargetPath>%(Filename)%(Extension)</TargetPath>
</PackagingOutputs>
</ItemGroup>
<!-- **END TERMINAL CONNECTION HACK** -->
<!-- Same thing again here, with WindowsTerminal.exe -->
<ItemGroup>
<_WindowsTerminalExe Include="$(OpenConsoleCommonOutDir)\WindowsTerminal\*.exe" />

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.7.3" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
</packages>

View File

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

View File

@@ -82,7 +82,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 +98,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),
@@ -113,7 +113,7 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const
// - This is an iterator over legacy colors only. The text is not modified.
// Arguments:
// - legacyAttrs - One legacy color item per cell
OutputCellIterator::OutputCellIterator(const std::span<const WORD> legacyAttrs) noexcept :
OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs) noexcept :
_mode(Mode::LegacyAttr),
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
_run(legacyAttrs),
@@ -128,7 +128,7 @@ OutputCellIterator::OutputCellIterator(const std::span<const WORD> legacyAttrs)
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
// Arguments:
// - charInfos - Multiple cell with unicode text and legacy color data.
OutputCellIterator::OutputCellIterator(const std::span<const CHAR_INFO> charInfos) noexcept :
OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept :
_mode(Mode::CharInfo),
_currentView(s_GenerateView(til::at(charInfos, 0))),
_run(charInfos),
@@ -143,7 +143,7 @@ OutputCellIterator::OutputCellIterator(const std::span<const CHAR_INFO> charInfo
// - This is an iterator over existing OutputCells with full text and color data.
// Arguments:
// - cells - Multiple cells in a run
OutputCellIterator::OutputCellIterator(const std::span<const OutputCell> cells) :
OutputCellIterator::OutputCellIterator(const gsl::span<const OutputCell> cells) :
_mode(Mode::Cell),
_currentView(s_GenerateView(til::at(cells, 0))),
_run(cells),
@@ -181,15 +181,15 @@ OutputCellIterator::operator bool() const noexcept
}
case Mode::Cell:
{
return _pos < std::get<std::span<const OutputCell>>(_run).size();
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
}
case Mode::CharInfo:
{
return _pos < std::get<std::span<const CHAR_INFO>>(_run).size();
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
}
case Mode::LegacyAttr:
{
return _pos < std::get<std::span<const WORD>>(_run).size();
return _pos < std::get<gsl::span<const WORD>>(_run).size();
}
default:
FAIL_FAST_HR(E_NOTIMPL);
@@ -198,11 +198,6 @@ OutputCellIterator::operator bool() const noexcept
CATCH_FAIL_FAST();
}
size_t OutputCellIterator::Position() const noexcept
{
return _pos;
}
// Routine Description:
// - Advances the iterator one position over the underlying data source.
// Return Value:
@@ -268,7 +263,7 @@ OutputCellIterator& OutputCellIterator::operator++()
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<std::span<const OutputCell>>(_run), _pos));
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
}
break;
}
@@ -278,7 +273,7 @@ OutputCellIterator& OutputCellIterator::operator++()
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<std::span<const CHAR_INFO>>(_run), _pos));
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
}
break;
}
@@ -288,7 +283,7 @@ OutputCellIterator& OutputCellIterator::operator++()
_pos++;
if (operator bool())
{
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<std::span<const WORD>>(_run), _pos));
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
}
break;
}
@@ -362,7 +357,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);
}
@@ -377,7 +372,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);
}
@@ -393,7 +389,9 @@ 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 dbcsAttr = IsGlyphFullWidth(glyph) ? DbcsAttribute::Leading : DbcsAttribute::Single;

View File

@@ -37,18 +37,17 @@ 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::span<const WORD> legacyAttributes) noexcept;
OutputCellIterator(const std::span<const CHAR_INFO> charInfos) noexcept;
OutputCellIterator(const std::span<const OutputCell> cells);
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);
~OutputCellIterator() = default;
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
operator bool() const noexcept;
size_t Position() const noexcept;
til::CoordType GetCellDistance(OutputCellIterator other) const noexcept;
til::CoordType GetInputDistance(OutputCellIterator other) const noexcept;
friend til::CoordType operator-(OutputCellIterator one, OutputCellIterator two) = delete;
@@ -87,13 +86,13 @@ private:
};
Mode _mode;
std::span<const WORD> _legacyAttrs;
gsl::span<const WORD> _legacyAttrs;
std::variant<
std::wstring_view,
std::span<const WORD>,
std::span<const CHAR_INFO>,
std::span<const OutputCell>,
gsl::span<const WORD>,
gsl::span<const CHAR_INFO>,
gsl::span<const OutputCell>,
std::monostate>
_run;
@@ -101,9 +100,15 @@ private:
bool _TryMoveTrailing() noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior);
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;

View File

@@ -34,9 +34,9 @@ OutputCellRect::OutputCellRect(const til::CoordType rows, const til::CoordType c
// - row - The Y position or row index in the buffer.
// Return Value:
// - Read/write span of OutputCells
std::span<OutputCell> OutputCellRect::GetRow(const til::CoordType row)
gsl::span<OutputCell> OutputCellRect::GetRow(const til::CoordType row)
{
return std::span<OutputCell>(_FindRowOffset(row), _cols);
return gsl::span<OutputCell>(_FindRowOffset(row), _cols);
}
// Routine Description:
@@ -47,7 +47,7 @@ std::span<OutputCell> OutputCellRect::GetRow(const til::CoordType row)
// - Read-only iterator of OutputCells
OutputCellIterator OutputCellRect::GetRowIter(const til::CoordType row) const
{
const std::span<const OutputCell> view(_FindRowOffset(row), _cols);
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
return OutputCellIterator(view);
}

View File

@@ -32,7 +32,7 @@ public:
OutputCellRect() noexcept;
OutputCellRect(const til::CoordType rows, const til::CoordType cols);
std::span<OutputCell> GetRow(const til::CoordType row);
gsl::span<OutputCell> GetRow(const til::CoordType row);
OutputCellIterator GetRowIter(const til::CoordType row) const;
til::CoordType Height() const noexcept;

View File

@@ -4,10 +4,7 @@
#include "precomp.h"
#include "Row.hpp"
#include <til/unicode.h>
#include "textBuffer.hpp"
#include "../../types/inc/GlyphWidth.hpp"
// The STL is missing a std::iota_n analogue for std::iota, so I made my own.
template<typename OutIt, typename Diff, typename T>
@@ -88,6 +85,19 @@ ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, c
}
}
void swap(ROW& lhs, ROW& rhs) noexcept
{
std::swap(lhs._charsBuffer, rhs._charsBuffer);
std::swap(lhs._charsHeap, rhs._charsHeap);
std::swap(lhs._chars, rhs._chars);
std::swap(lhs._charOffsets, rhs._charOffsets);
std::swap(lhs._attr, rhs._attr);
std::swap(lhs._columnCount, rhs._columnCount);
std::swap(lhs._lineRendition, rhs._lineRendition);
std::swap(lhs._wrapForced, rhs._wrapForced);
std::swap(lhs._doubleBytePadded, rhs._doubleBytePadded);
}
void ROW::SetWrapForced(const bool wrap) noexcept
{
_wrapForced = wrap;
@@ -141,46 +151,84 @@ void ROW::_init() noexcept
std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 });
}
// Routine Description:
// - resizes ROW to new width
// Arguments:
// - charsBuffer - a new backing buffer to use for _charsBuffer
// - charOffsetsBuffer - a new backing buffer to use for _charOffsets
// - rowWidth - the new width, in cells
// - fillAttribute - the attribute to use for any newly added, trailing cells
void ROW::Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute)
{
// A default-constructed ROW has no cols/chars to copy.
// It can be detected by the lack of a _charsBuffer (among others).
//
// Otherwise, this block figures out how much we can copy into the new `rowWidth`.
uint16_t colsToCopy = 0;
uint16_t charsToCopy = 0;
if (_charsBuffer)
{
colsToCopy = std::min(rowWidth, _columnCount);
// Safety: colsToCopy is [0, _columnCount].
charsToCopy = _uncheckedCharOffset(colsToCopy);
// Safety: colsToCopy is [0, _columnCount] due to colsToCopy != 0.
for (; colsToCopy != 0 && _uncheckedIsTrailer(colsToCopy); --colsToCopy)
{
}
}
// If we grow the row width, we have to append a bunch of whitespace.
// `trailingWhitespace` stores that amount.
// Safety: The preceding block left colsToCopy in the range [0, rowWidth].
const uint16_t trailingWhitespace = rowWidth - colsToCopy;
// Allocate memory for the new `_chars` array.
// Use the provided charsBuffer if possible, otherwise allocate a `_charsHeap`.
std::unique_ptr<wchar_t[]> charsHeap;
std::span chars{ charsBuffer, rowWidth };
const std::span charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u };
if (const uint16_t charsCapacity = charsToCopy + trailingWhitespace; charsCapacity > rowWidth)
{
charsHeap = std::make_unique_for_overwrite<wchar_t[]>(charsCapacity);
chars = { charsHeap.get(), charsCapacity };
}
// Copy chars and charOffsets over.
{
const auto it = std::copy_n(_chars.begin(), charsToCopy, chars.begin());
std::fill_n(it, trailingWhitespace, L' ');
}
{
const auto it = std::copy_n(_charOffsets.begin(), colsToCopy, charOffsets.begin());
// The _charOffsets array is 1 wider than newWidth indicates.
// This is because the extra column contains the past-the-end index into _chars.
iota_n(it, trailingWhitespace + 1u, charsToCopy);
}
_charsBuffer = charsBuffer;
_charsHeap = std::move(charsHeap);
_chars = chars;
_charOffsets = charOffsets;
_columnCount = rowWidth;
// .resize_trailing_extent() doesn't work if the vector is empty,
// since there's no trailing item that could be extended.
if (_attr.empty())
{
_attr = { rowWidth, fillAttribute };
}
else
{
_attr.resize_trailing_extent(rowWidth);
}
}
void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth)
{
_attr = attr;
_attr.resize_trailing_extent(gsl::narrow<uint16_t>(newWidth));
}
// Returns the previous possible cursor position, preceding the given column.
// Returns 0 if column is less than or equal to 0.
til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept
{
return _adjustBackward(_clampedColumn(column - 1));
}
// Returns the next possible cursor position, following the given column.
// Returns the row width if column is beyond the width of the row.
til::CoordType ROW::NavigateToNext(til::CoordType column) const noexcept
{
return _adjustForward(_clampedColumn(column + 1));
}
uint16_t ROW::_adjustBackward(uint16_t column) const noexcept
{
// Safety: This is a little bit more dangerous. The first column is supposed
// to never be a trailer and so this loop should exit if column == 0.
for (; _uncheckedIsTrailer(column); --column)
{
}
return column;
}
uint16_t ROW::_adjustForward(uint16_t column) const noexcept
{
// Safety: This is a little bit more dangerous. The last column is supposed
// to never be a trailer and so this loop should exit if column == _columnCount.
for (; _uncheckedIsTrailer(column); ++column)
{
}
return column;
}
// Routine Description:
// - clears char data in column in row
// Arguments:
@@ -263,20 +311,16 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c
}
break;
case DbcsAttribute::Trailing:
// Handling the trailing half of wide chars ensures that we correctly restore
// wide characters when a user backs up and restores the viewport via CHAR_INFOs.
if (fillingFirstColumn)
{
// The wide char doesn't fit. Pad with whitespace.
// Ignore the character. There's no correct alternative way to handle this situation.
ClearCell(currentIndex);
}
else if (it.Position() == 0)
else
{
// A common way to back up and restore the buffer is via `ReadConsoleOutputW` and
// `WriteConsoleOutputW` respectively. But the area might bisect/intersect/clip wide characters and
// only backup either their leading or trailing half. In general, in the rest of conhost, we're
// throwing away the trailing half of all `CHAR_INFO`s (during text rendering, as well as during
// `ReadConsoleOutputW`), so to make this code behave the same and prevent surprises, we need to
// make sure to only look at the trailer if it's the first `CHAR_INFO` the user is trying to write.
ReplaceCharacters(currentIndex - 1, 2, chars);
}
++it;
@@ -327,245 +371,90 @@ void ROW::ReplaceAttributes(const til::CoordType beginIndex, const til::CoordTyp
_attr.replace(_clampedColumnInclusive(beginIndex), _clampedColumnInclusive(endIndex), newAttr);
}
[[msvc::forceinline]] ROW::WriteHelper::WriteHelper(ROW& row, til::CoordType columnBegin, til::CoordType columnLimit, const std::wstring_view& chars) noexcept :
row{ row },
chars{ chars }
{
colBeg = row._clampedColumnInclusive(columnBegin);
colLimit = row._clampedColumnInclusive(columnLimit);
chBegDirty = row._uncheckedCharOffset(colBeg);
colBegDirty = row._adjustBackward(colBeg);
leadingSpaces = colBeg - colBegDirty;
chBeg = chBegDirty + leadingSpaces;
colEnd = colBeg;
colEndDirty = 0;
charsConsumed = 0;
}
[[msvc::forceinline]] bool ROW::WriteHelper::IsValid() const noexcept
{
return colBeg < colLimit && !chars.empty();
}
void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars)
try
{
WriteHelper h{ *this, columnBegin, _columnCount, chars };
if (!h.IsValid())
const auto colBeg = _clampedUint16(columnBegin);
const auto colEnd = _clampedUint16(columnBegin + width);
if (colBeg >= colEnd || colEnd > _columnCount || chars.empty())
{
return;
}
h.ReplaceCharacters(width);
h.Finish();
}
catch (...)
{
// Due to this function writing _charOffsets first, then calling _resizeChars (which may throw) and only then finally
// filling in _chars, we might end up in a situation were _charOffsets contains offsets outside of the _chars array.
// --> Restore this row to a known "okay"-state.
Reset(TextAttribute{});
throw;
}
[[msvc::forceinline]] void ROW::WriteHelper::ReplaceCharacters(til::CoordType width) noexcept
{
const auto colEndNew = gsl::narrow_cast<uint16_t>(colEnd + width);
if (colEndNew > colLimit)
{
colEndDirty = colLimit;
}
else
{
til::at(row._charOffsets, colEnd++) = chBeg;
for (; colEnd < colEndNew; ++colEnd)
{
til::at(row._charOffsets, colEnd) = gsl::narrow_cast<uint16_t>(chBeg | CharOffsetsTrailer);
}
// Safety:
// * colBeg is now [0, _columnCount)
// * colEnd is now (colBeg, _columnCount]
colEndDirty = colEnd;
charsConsumed = chars.size();
}
}
void ROW::ReplaceText(RowWriteState& state)
try
{
WriteHelper h{ *this, state.columnBegin, state.columnLimit, state.text };
if (!h.IsValid())
{
state.columnEnd = h.colBeg;
state.columnBeginDirty = h.colBeg;
state.columnEndDirty = h.colBeg;
return;
}
h.ReplaceText();
h.Finish();
state.text = state.text.substr(h.charsConsumed);
// Here's why we set `state.columnEnd` to `colLimit` if there's remaining text:
// Callers should be able to use `state.columnEnd` as the next cursor position, as well as the parameter for a
// follow-up call to ReplaceAttributes(). But if we fail to insert a wide glyph into the last column of a row,
// that last cell (which now contains padding whitespace) should get the same attributes as the rest of the
// string so that the row looks consistent. This requires us to return `colLimit` instead of `colLimit - 1`.
// Additionally, this has the benefit that callers can detect line wrapping by checking `columnEnd >= columnLimit`.
state.columnEnd = state.text.empty() ? h.colEnd : h.colLimit;
state.columnBeginDirty = h.colBegDirty;
state.columnEndDirty = h.colEndDirty;
}
catch (...)
{
Reset(TextAttribute{});
throw;
}
[[msvc::forceinline]] void ROW::WriteHelper::ReplaceText() noexcept
{
size_t ch = chBeg;
for (const auto& s : til::utf16_iterator{ chars })
{
const auto wide = til::at(s, 0) < 0x80 ? false : IsGlyphFullWidth(s);
const auto colEndNew = gsl::narrow_cast<uint16_t>(colEnd + 1u + wide);
if (colEndNew > colLimit)
{
colEndDirty = colLimit;
break;
}
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch);
if (wide)
{
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch | CharOffsetsTrailer);
}
colEndDirty = colEnd;
ch += s.size();
}
charsConsumed = ch - chBeg;
}
til::CoordType ROW::CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit)
try
{
const auto otherColBeg = other._clampedColumnInclusive(otherBegin);
const auto otherColLimit = other._clampedColumnInclusive(otherLimit);
std::span<uint16_t> charOffsets;
std::wstring_view chars;
if (otherColBeg < otherColLimit)
{
charOffsets = other._charOffsets.subspan(otherColBeg, static_cast<size_t>(otherColLimit) - otherColBeg + 1);
const auto charsOffset = charOffsets.front() & CharOffsetsMask;
// We _are_ using span. But C++ decided that string_view and span aren't convertible.
// _chars is a std::span for performance and because it refers to raw, shared memory.
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
chars = { other._chars.data() + charsOffset, other._chars.size() - charsOffset };
}
WriteHelper h{ *this, columnBegin, columnLimit, chars };
if (!h.IsValid())
{
return h.colBeg;
}
// Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd
// element is the length of the first glyph) and begins/ends with a non-trailer offset. We don't really
// need to test for the end offset, since `WriteHelper::WriteWithOffsets` already takes care of that.
if (charOffsets.size() < 2 || WI_IsFlagSet(charOffsets.front(), CharOffsetsTrailer))
{
assert(false);
otherBegin = other.size();
return h.colBeg;
}
h.CopyRangeFrom(charOffsets);
h.Finish();
otherBegin += h.colEnd - h.colBeg;
return h.colEndDirty;
}
catch (...)
{
Reset(TextAttribute{});
throw;
}
[[msvc::forceinline]] void ROW::WriteHelper::CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept
{
// Since our `charOffsets` input is already in columns (just like the `ROW::_charOffsets`),
// we can directly look up the end char-offset, but...
const auto colEndDirtyInput = std::min(gsl::narrow_cast<uint16_t>(colLimit - colBeg), gsl::narrow<uint16_t>(charOffsets.size() - 1));
// ...since the colLimit might intersect with a wide glyph in `charOffset`, we need to adjust our input-colEnd.
auto colEndInput = colEndDirtyInput;
for (; WI_IsFlagSet(til::at(charOffsets, colEndInput), CharOffsetsTrailer); --colEndInput)
{
}
const auto baseOffset = til::at(charOffsets, 0);
const auto endOffset = til::at(charOffsets, colEndInput);
const auto inToOutOffset = gsl::narrow_cast<uint16_t>(chBeg - baseOffset);
// Now with the `colEndInput` figured out, we can easily copy the `charOffsets` into the `_charOffsets`.
// It's possible to use SIMD for this loop for extra perf gains. Something like this for SSE2 (~8x faster):
// const auto in = _mm_loadu_si128(...);
// const auto off = _mm_and_epi32(in, _mm_set1_epi16(CharOffsetsMask));
// const auto trailer = _mm_and_epi32(in, _mm_set1_epi16(CharOffsetsTrailer));
// const auto out = _mm_or_epi32(_mm_add_epi16(off, _mm_set1_epi16(inToOutOffset)), trailer);
// _mm_store_si128(..., out);
for (uint16_t i = 0; i < colEndInput; ++i, ++colEnd)
{
const auto ch = til::at(charOffsets, i);
const auto off = ch & CharOffsetsMask;
const auto trailer = ch & CharOffsetsTrailer;
til::at(row._charOffsets, colEnd) = gsl::narrow_cast<uint16_t>((off + inToOutOffset) | trailer);
}
colEndDirty = gsl::narrow_cast<uint16_t>(colBeg + colEndDirtyInput);
charsConsumed = endOffset - baseOffset;
}
[[msvc::forceinline]] void ROW::WriteHelper::Finish()
{
colEndDirty = row._adjustForward(colEndDirty);
const uint16_t trailingSpaces = colEndDirty - colEnd;
const auto chEndDirtyOld = row._uncheckedCharOffset(colEndDirty);
const auto chEndDirty = chBegDirty + charsConsumed + leadingSpaces + trailingSpaces;
if (chEndDirty != chEndDirtyOld)
{
row._resizeChars(colEndDirty, chBegDirty, chEndDirty, chEndDirtyOld);
}
{
// std::copy_n compiles to memmove. We can do better. It also gets rid of an extra branch,
// because std::copy_n avoids calling memmove if the count is 0. It's never 0 for us.
const auto itBeg = row._chars.begin() + chBeg;
memcpy(&*itBeg, chars.data(), charsConsumed * sizeof(wchar_t));
if (leadingSpaces)
{
fill_n_small(row._chars.begin() + chBegDirty, leadingSpaces, L' ');
iota_n(row._charOffsets.begin() + colBegDirty, leadingSpaces, chBegDirty);
}
if (trailingSpaces)
{
fill_n_small(itBeg + charsConsumed, trailingSpaces, L' ');
iota_n(row._charOffsets.begin() + colEnd, trailingSpaces, gsl::narrow_cast<uint16_t>(chBeg + charsConsumed));
}
}
// This updates `_doubleBytePadded` whenever we write the last column in the row. `_doubleBytePadded` tells our text
// reflow algorithm whether it should ignore the last column. This is important when writing wide characters into
// the terminal: If the last wide character in a row only fits partially, we should render whitespace, but
// during text reflow pretend as if no whitespace exists. After all, the user didn't write any whitespace there.
// Algorithm explanation
//
// The way this is written, it'll set `_doubleBytePadded` to `true` no matter whether a wide character didn't fit,
// or if the last 2 columns contain a wide character and a narrow character got written into the left half of it.
// In both cases `trailingSpaces` is 1 and fills the last column and `_doubleBytePadded` will be `true`.
if (colEndDirty == row._columnCount)
// Task:
// Replace the characters in cells [colBeg, colEnd) with a single `width`-wide glyph consisting of `chars`.
//
// Problem:
// Imagine that we have the following ROW contents:
// "xxyyzz"
// xx, yy, zz are 2 cell wide glyphs. We want to insert a 2 cell wide glyph ww at colBeg 1:
// ^^
// ww
// An incorrect result would be:
// "xwwyzz"
// The half cut off x and y glyph wouldn't make much sense, so we need to fill them with whitespace:
// " ww zz"
//
// Solution:
// Given the range we want to replace [colBeg, colEnd), we "extend" it to encompass leading (preceding)
// and trailing wide glyphs we partially overwrite resulting in the range [colExtBeg, colExtEnd), where
// colExtBeg <= colBeg and colExtEnd >= colEnd. In other words, the to be replaced range has been "extended".
// The amount of leading whitespace we need to insert is thus colBeg - colExtBeg
// and the amount of trailing whitespace colExtEnd - colEnd.
// Extend range downwards (leading whitespace)
uint16_t colExtBeg = colBeg;
// Safety: colExtBeg is [0, _columnCount], because colBeg is.
const uint16_t chExtBeg = _uncheckedCharOffset(colExtBeg);
// Safety: colExtBeg remains [0, _columnCount] due to colExtBeg != 0.
for (; colExtBeg != 0 && _uncheckedIsTrailer(colExtBeg); --colExtBeg)
{
row.SetDoubleBytePadded(colEnd < row._columnCount);
}
// Extend range upwards (trailing whitespace)
uint16_t colExtEnd = colEnd;
// Safety: colExtEnd cannot be incremented past _columnCount, because the last
// _charOffset at index _columnCount will never get the CharOffsetsTrailer flag.
for (; _uncheckedIsTrailer(colExtEnd); ++colExtEnd)
{
}
// Safety: After the previous loop colExtEnd is [0, _columnCount].
const uint16_t chExtEnd = _uncheckedCharOffset(colExtEnd);
const uint16_t leadingSpaces = colBeg - colExtBeg;
const uint16_t trailingSpaces = colExtEnd - colEnd;
const size_t chExtEndNew = chars.size() + leadingSpaces + trailingSpaces + chExtBeg;
if (chExtEndNew != chExtEnd)
{
_resizeChars(colExtEnd, chExtBeg, chExtEnd, chExtEndNew);
}
// Add leading/trailing whitespace and copy chars
{
auto it = _chars.begin() + chExtBeg;
it = fill_n_small(it, leadingSpaces, L' ');
it = copy_n_small(chars.begin(), chars.size(), it);
it = fill_n_small(it, trailingSpaces, L' ');
}
// Update char offsets with leading/trailing whitespace and the chars columns.
{
auto chPos = chExtBeg;
auto it = _charOffsets.begin() + colExtBeg;
it = iota_n_mut(it, leadingSpaces, chPos);
*it++ = chPos;
it = fill_small(it, _charOffsets.begin() + colEnd, gsl::narrow_cast<uint16_t>(chPos | CharOffsetsTrailer));
chPos = gsl::narrow_cast<uint16_t>(chPos + chars.size());
it = iota_n_mut(it, trailingSpaces, chPos);
}
}
@@ -573,15 +462,15 @@ catch (...)
// as it reallocates the backing buffer and shifts the char offsets.
// The parameters are difficult to explain, but their names are identical to
// local variables in ReplaceCharacters() which I've attempted to document there.
void ROW::_resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDirty, uint16_t chEndDirtyOld)
void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew)
{
const auto diff = chEndDirty - chEndDirtyOld;
const auto diff = chExtEndNew - chExtEnd;
const auto currentLength = _charSize();
const auto newLength = currentLength + diff;
if (newLength <= _chars.size())
{
std::copy_n(_chars.begin() + chEndDirtyOld, currentLength - chEndDirtyOld, _chars.begin() + chEndDirty);
std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, _chars.begin() + chExtEndNew);
}
else
{
@@ -591,14 +480,14 @@ void ROW::_resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDi
auto charsHeap = std::make_unique_for_overwrite<wchar_t[]>(newCapacity);
const std::span chars{ charsHeap.get(), newCapacity };
std::copy_n(_chars.begin(), chBegDirty, chars.begin());
std::copy_n(_chars.begin() + chEndDirtyOld, currentLength - chEndDirtyOld, chars.begin() + chEndDirty);
std::copy_n(_chars.begin(), chExtBeg, chars.begin());
std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, chars.begin() + chExtEndNew);
_charsHeap = std::move(charsHeap);
_chars = chars;
}
auto it = _charOffsets.begin() + colEndDirty;
auto it = _charOffsets.begin() + colExtEnd;
const auto end = _charOffsets.end();
for (; it != end; ++it)
{
@@ -606,11 +495,6 @@ void ROW::_resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDi
}
}
til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() noexcept
{
return _attr;
}
const til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() const noexcept
{
return _attr;
@@ -639,12 +523,6 @@ uint16_t ROW::size() const noexcept
return _columnCount;
}
til::CoordType ROW::LineRenditionColumns() const noexcept
{
const auto scale = _lineRendition != LineRendition::SingleWidth ? 1 : 0;
return _columnCount >> scale;
}
til::CoordType ROW::MeasureLeft() const noexcept
{
const auto text = GetText();
@@ -799,13 +677,11 @@ uint16_t ROW::_charSize() const noexcept
// Safety: col must be [0, _columnCount].
uint16_t ROW::_uncheckedCharOffset(size_t col) const noexcept
{
assert(col < _charOffsets.size());
return til::at(_charOffsets, col) & CharOffsetsMask;
}
// Safety: col must be [0, _columnCount].
bool ROW::_uncheckedIsTrailer(size_t col) const noexcept
{
assert(col < _charOffsets.size());
return WI_IsFlagSet(til::at(_charOffsets, col), CharOffsetsTrailer);
}

View File

@@ -20,6 +20,8 @@ Revision History:
#pragma once
#include <span>
#include <til/rle.h>
#include "LineRendition.hpp"
@@ -35,28 +37,6 @@ enum class DelimiterClass
RegularChar
};
struct RowWriteState
{
// The text you want to write into the given ROW. When ReplaceText() returns,
// this is updated to remove all text from the beginning that was successfully written.
std::wstring_view text; // IN/OUT
// The column at which to start writing.
til::CoordType columnBegin = 0; // IN
// The first column which should not be written to anymore.
til::CoordType columnLimit = 0; // IN
// The column 1 past the last glyph that was successfully written into the row. If you need to call
// ReplaceAttributes() to colorize the written range, etc., this is the columnEnd parameter you want.
// If you want to continue writing where you left off, this is also the next columnBegin parameter.
til::CoordType columnEnd = 0; // OUT
// The first column that got modified by this write operation. In case that the first glyph we write overwrites
// the trailing half of a wide glyph, leadingSpaces will be 1 and this value will be 1 less than colBeg.
til::CoordType columnBeginDirty = 0; // OUT
// This is 1 past the last column that was modified and will be 1 past columnEnd if we overwrote
// the leading half of a wide glyph and had to fill the trailing half with whitespace.
til::CoordType columnEndDirty = 0; // OUT
};
class ROW final
{
public:
@@ -66,9 +46,11 @@ public:
ROW(const ROW& other) = delete;
ROW& operator=(const ROW& other) = delete;
ROW(ROW&& other) = default;
explicit ROW(ROW&& other) = default;
ROW& operator=(ROW&& other) = default;
friend void swap(ROW& lhs, ROW& rhs) noexcept;
void SetWrapForced(const bool wrap) noexcept;
bool WasWrapForced() const noexcept;
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept;
@@ -77,25 +59,19 @@ public:
LineRendition GetLineRendition() const noexcept;
void Reset(const TextAttribute& attr);
void Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
void ClearCell(til::CoordType column);
OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional<bool> wrap = std::nullopt, std::optional<til::CoordType> limitRight = std::nullopt);
bool SetAttrToEnd(til::CoordType columnBegin, TextAttribute attr);
void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr);
void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars);
void ReplaceText(RowWriteState& state);
til::CoordType CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit);
til::small_rle<TextAttribute, uint16_t, 1>& Attributes() noexcept;
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
TextAttribute GetAttrByColumn(til::CoordType column) const;
std::vector<uint16_t> GetHyperlinks() const;
uint16_t size() const noexcept;
til::CoordType LineRenditionColumns() const noexcept;
til::CoordType MeasureLeft() const noexcept;
til::CoordType MeasureRight() const noexcept;
bool ContainsText() const noexcept;
@@ -113,50 +89,6 @@ public:
#endif
private:
// WriteHelper exists because other forms of abstracting this functionality away (like templates with lambdas)
// where only very poorly optimized by MSVC as it failed to inline the templates.
struct WriteHelper
{
explicit WriteHelper(ROW& row, til::CoordType columnBegin, til::CoordType columnLimit, const std::wstring_view& chars) noexcept;
bool IsValid() const noexcept;
void ReplaceCharacters(til::CoordType width) noexcept;
void ReplaceText() noexcept;
void CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept;
void Finish();
// Parent pointer.
ROW& row;
// The text given by the caller.
const std::wstring_view& chars;
// This is the same as the columnBegin parameter for ReplaceText(), etc.,
// but clamped to a valid range via _clampedColumnInclusive.
uint16_t colBeg;
// This is the same as the columnLimit parameter for ReplaceText(), etc.,
// but clamped to a valid range via _clampedColumnInclusive.
uint16_t colLimit;
// The column 1 past the last glyph that was successfully written into the row. If you need to call
// ReplaceAttributes() to colorize the written range, etc., this is the columnEnd parameter you want.
// If you want to continue writing where you left off, this is also the next columnBegin parameter.
uint16_t colEnd;
// The first column that got modified by this write operation. In case that the first glyph we write overwrites
// the trailing half of a wide glyph, leadingSpaces will be 1 and this value will be 1 less than colBeg.
uint16_t colBegDirty;
// Similar to dirtyBeg, this is 1 past the last column that was modified and will be 1 past colEnd if
// we overwrote the leading half of a wide glyph and had to fill the trailing half with whitespace.
uint16_t colEndDirty;
// The offset in ROW::chars at which we start writing the contents of WriteHelper::chars.
uint16_t chBeg;
// The offset at which we start writing leadingSpaces-many whitespaces.
uint16_t chBegDirty;
// The same as `colBeg - colBegDirty`. This is the amount of whitespace
// we write at chBegDirty, before the actual WriteHelper::chars content.
uint16_t leadingSpaces;
// The amount of characters copied from WriteHelper::chars.
size_t charsConsumed;
};
// To simplify the detection of wide glyphs, we don't just store the simple character offset as described
// for _charOffsets. Instead we use the most significant bit to indicate whether any column is the
// trailing half of a wide glyph. This simplifies many implementation details via _uncheckedIsTrailer.
@@ -170,16 +102,13 @@ private:
template<typename T>
constexpr uint16_t _clampedColumnInclusive(T v) const noexcept;
uint16_t _adjustBackward(uint16_t column) const noexcept;
uint16_t _adjustForward(uint16_t column) const noexcept;
wchar_t _uncheckedChar(size_t off) const noexcept;
uint16_t _charSize() const noexcept;
uint16_t _uncheckedCharOffset(size_t col) const noexcept;
bool _uncheckedIsTrailer(size_t col) const noexcept;
void _init() noexcept;
void _resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDirty, uint16_t chEndDirtyOld);
void _resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew);
// These fields are a bit "wasteful", but it makes all this a bit more robust against
// programming errors during initial development (which is when this comment was written).

View File

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

View File

@@ -76,7 +76,7 @@ public:
void CopyProperties(const Cursor& OtherCursor) noexcept;
void DelayEOLWrap() noexcept;
void DelayEOLWrap(const til::point coordDelayedAt) noexcept;
void ResetDelayEOLWrap() noexcept;
til::point GetDelayedAtPosition() const noexcept;
bool IsDelayedEOLWrap() const noexcept;

View File

@@ -1,7 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- precomp.h
Abstract:
- Contains external headers to include in the precompile phase of console build process.
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
--*/
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#include <LibraryIncludes.h>
// clang-format off
#include <unicode.hpp>
// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"
#pragma warning(push)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define NOMCX
#define NOHELP
#define NOCOMM
#endif
// Windows Header Files:
#include <windows.h>
#include <intsafe.h>
// private dependencies
#include "../inc/unicode.hpp"
#pragma warning(pop)
// clang-format on

View File

@@ -18,19 +18,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 +41,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 +55,7 @@ Search::Search(Microsoft::Console::Render::IRenderData& renderData,
_sensitivity(sensitivity),
_needle(s_CreateNeedleFromString(str)),
_coordAnchor(anchor),
_renderData(renderData)
_uiaData(uiaData)
{
_coordNext = _coordAnchor;
}
@@ -99,10 +99,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 +114,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 +135,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 +158,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;
}
@@ -196,7 +196,7 @@ bool Search::_FindNeedleInHaystackAt(const til::point pos, til::point& start, ti
for (const auto& needleChars : _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;
// If we didn't match at any point of the needle, return false.
@@ -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)
{

View File

@@ -17,9 +17,10 @@ Revision History:
#pragma once
#include <WinConTypes.h>
#include "TextAttribute.hpp"
#include "textBuffer.hpp"
#include "../renderer/inc/IRenderData.hpp"
#include "../types/IUiaData.h"
// This used to be in find.h.
#define SEARCH_STRING_LENGTH (80)
@@ -39,12 +40,12 @@ public:
CaseSensitive
};
Search(Microsoft::Console::Render::IRenderData& renderData,
Search(Microsoft::Console::Types::IUiaData& uiaData,
const std::wstring_view str,
const Direction dir,
const Sensitivity sensitivity);
Search(Microsoft::Console::Render::IRenderData& renderData,
Search(Microsoft::Console::Types::IUiaData& uiaData,
const std::wstring_view str,
const Direction dir,
const Sensitivity sensitivity,
@@ -65,7 +66,7 @@ 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);
@@ -78,7 +79,7 @@ private:
const std::vector<std::wstring> _needle;
const Direction _direction;
const Sensitivity _sensitivity;
Microsoft::Console::Render::IRenderData& _renderData;
Microsoft::Console::Types::IUiaData& _uiaData;
#ifdef UNIT_TESTING
friend class SearchTests;

View File

@@ -13,6 +13,75 @@
#include "../types/inc/convert.hpp"
#include "../../types/inc/GlyphWidth.hpp"
namespace
{
struct BufferAllocator
{
BufferAllocator(til::size sz)
{
const auto w = gsl::narrow<uint16_t>(sz.width);
const auto h = gsl::narrow<uint16_t>(sz.height);
const auto charsBytes = w * sizeof(wchar_t);
// The ROW::_indices array stores 1 more item than the buffer is wide.
// That extra column stores the past-the-end _chars pointer.
const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t);
const auto rowStride = charsBytes + indicesBytes;
// 65535*65535 cells would result in a charsAreaSize of 8GiB.
// --> Use uint64_t so that we can safely do our calculations even on x86.
const auto allocSize = gsl::narrow<size_t>(::base::strict_cast<uint64_t>(rowStride) * ::base::strict_cast<uint64_t>(h));
_buffer = wil::unique_virtualalloc_ptr<std::byte>{ static_cast<std::byte*>(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) };
THROW_IF_NULL_ALLOC(_buffer);
_data = std::span{ _buffer.get(), allocSize }.begin();
_rowStride = rowStride;
_indicesOffset = charsBytes;
_width = w;
_height = h;
}
BufferAllocator& operator++() noexcept
{
_data += _rowStride;
return *this;
}
wchar_t* chars() const noexcept
{
return til::bit_cast<wchar_t*>(&*_data);
}
uint16_t* indices() const noexcept
{
return til::bit_cast<uint16_t*>(&*(_data + _indicesOffset));
}
uint16_t width() const noexcept
{
return _width;
}
uint16_t height() const noexcept
{
return _height;
}
wil::unique_virtualalloc_ptr<std::byte>&& take() noexcept
{
return std::move(_buffer);
}
private:
wil::unique_virtualalloc_ptr<std::byte> _buffer;
std::span<std::byte>::iterator _data;
size_t _rowStride;
size_t _indicesOffset;
uint16_t _width;
uint16_t _height;
};
}
using namespace Microsoft::Console;
using namespace Microsoft::Console::Types;
@@ -29,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,
@@ -39,10 +108,15 @@ 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);
_charBuffer = _allocateBuffer(screenBufferSize, _currentAttributes, _storage);
BufferAllocator allocator{ screenBufferSize };
_storage.reserve(allocator.height());
for (til::CoordType i = 0; i < screenBufferSize.Y; ++i, ++allocator)
{
_storage.emplace_back(allocator.chars(), allocator.indices(), allocator.width(), _currentAttributes);
}
_charBuffer = allocator.take();
_UpdateSize();
}
@@ -141,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));
}
@@ -188,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 (...)
{
@@ -245,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 (...)
{
@@ -272,19 +346,23 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// - false otherwise (out of memory)
bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute)
{
// This function corrects most errors. If this is false, we had an uncorrectable one which
// older versions of conhost simply let pass by unflinching.
LOG_HR_IF(E_NOT_VALID_STATE, !(_AssertValidDoubleByteSequence(dbcsAttribute))); // Shouldn't be uncorrectable sequences unless something is very wrong.
auto fSuccess = true;
// Now compensate if we don't have enough space for the upcoming double byte sequence
// We only need to compensate for leading bytes
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
@@ -294,32 +372,6 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
return fSuccess;
}
void TextBuffer::ConsumeGrapheme(std::wstring_view& chars) noexcept
{
// This function is supposed to mirror the behavior of ROW::Write, when it reads characters off of `chars`.
// (I know that a UTF-16 code point is not a grapheme, but that's what we're working towards.)
chars = til::utf16_pop(chars);
}
// This function is intended for writing regular "lines" of text and only the `state.text` and`state.columnBegin`
// fields are being used, whereas `state.columnLimit` is automatically overwritten by the line width of the given row.
// This allows this function to automatically set the wrap-forced field of the row, which is also the return value.
// The return value indicates to the caller whether the cursor should be moved to the next line.
void TextBuffer::WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state)
{
auto& r = GetRowByOffset(row);
r.ReplaceText(state);
r.ReplaceAttributes(state.columnBegin, state.columnEnd, attributes);
if (state.columnEnd >= state.columnLimit)
{
r.SetWrapForced(wrapAtEOL);
}
TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, state.columnEndDirty, row + 1 }));
}
// Routine Description:
// - Writes cells to the output buffer. Writes at the cursor.
// Arguments:
@@ -365,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;
@@ -393,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);
@@ -423,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);
@@ -498,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);
@@ -517,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();
@@ -551,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);
@@ -625,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;
}
@@ -663,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;
}
}
@@ -693,37 +745,6 @@ const Viewport TextBuffer::GetSize() const noexcept
return _size;
}
wil::unique_virtualalloc_ptr<std::byte> TextBuffer::_allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector<ROW>& rows)
{
const auto w = gsl::narrow<uint16_t>(sz.width);
const auto h = gsl::narrow<uint16_t>(sz.height);
const auto charsBytes = w * sizeof(wchar_t);
// The ROW::_indices array stores 1 more item than the buffer is wide.
// That extra column stores the past-the-end _chars pointer.
const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t);
const auto rowStride = charsBytes + indicesBytes;
// 65535*65535 cells would result in a charsAreaSize of 8GiB.
// --> Use uint64_t so that we can safely do our calculations even on x86.
const auto allocSize = gsl::narrow<size_t>(::base::strict_cast<uint64_t>(rowStride) * ::base::strict_cast<uint64_t>(h));
auto buffer = wil::unique_virtualalloc_ptr<std::byte>{ static_cast<std::byte*>(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) };
THROW_IF_NULL_ALLOC(buffer);
auto data = std::span{ buffer.get(), allocSize }.begin();
rows.resize(h);
for (auto& row : rows)
{
const auto chars = til::bit_cast<wchar_t*>(&*data);
const auto indices = til::bit_cast<uint16_t*>(&*(data + charsBytes));
row = { chars, indices, w, attributes };
data += rowStride;
}
return buffer;
}
void TextBuffer::_UpdateSize()
{
_size = Viewport::FromDimensions({ _storage.at(0).size(), gsl::narrow<til::CoordType>(_storage.size()) });
@@ -854,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)
{
@@ -905,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:
@@ -942,63 +963,41 @@ 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 };
const auto currentSize = GetSize().Dimensions();
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 = gsl::narrow_cast<size_t>(_firstRow + TopRow) % _storage.size();
std::vector<ROW> newStorage;
auto newBuffer = _allocateBuffer(newSize, _currentAttributes, newStorage);
// This basically imitates a std::rotate_copy(first, mid, last), but uses ROW::CopyRangeFrom() to do the copying.
{
const auto first = _storage.begin();
const auto last = _storage.end();
const auto mid = first + TopRowIndex;
auto dest = newStorage.begin();
std::span<ROW> sourceRanges[]{
{ mid, last },
{ first, mid },
};
// Ensure we don't copy more from `_storage` than fit into `newStorage`.
if (sourceRanges[0].size() > newStorage.size())
{
sourceRanges[0] = sourceRanges[0].subspan(0, newStorage.size());
}
if (const auto remaining = newStorage.size() - sourceRanges[0].size(); sourceRanges[1].size() > remaining)
{
sourceRanges[1] = sourceRanges[1].subspan(0, remaining);
}
for (const auto& sourceRange : sourceRanges)
{
for (const auto& oldRow : sourceRange)
{
til::CoordType begin = 0;
dest->CopyRangeFrom(0, til::CoordTypeMax, oldRow, begin, til::CoordTypeMax);
dest->TransferAttributes(oldRow.Attributes(), newSize.width);
++dest;
}
}
}
_charBuffer = std::move(newBuffer);
_storage = std::move(newStorage);
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());
_SetFirstRowIndex(0);
// realloc in the Y direction
// remove rows if we're shrinking
_storage.resize(allocator.height());
// realloc in the X direction
for (auto& it : _storage)
{
it.Resize(allocator.chars(), allocator.indices(), allocator.width(), attributes);
++allocator;
}
// Update the cached size value
_UpdateSize();
_charBuffer = allocator.take();
}
CATCH_RETURN();
@@ -1089,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:
@@ -1206,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);
}
@@ -1324,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;
}
@@ -1333,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);
}
@@ -1607,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
@@ -1666,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);
}
}
@@ -1688,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);
}
@@ -1717,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);
}
@@ -1728,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);
}
@@ -1743,7 +1742,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
{
bufferSize.IncrementInBounds(targetPoint);
}
textRow.right = targetPoint.x;
textRow.Right = targetPoint.X;
}
}
@@ -1778,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));
@@ -2330,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;
@@ -2349,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());
}
@@ -2386,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;
@@ -2428,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.
@@ -2459,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;
}
}
@@ -2468,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;
}
}
@@ -2483,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;
@@ -2523,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;
}
@@ -2539,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 (;
@@ -2579,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);
}
@@ -2593,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);
}

View File

@@ -89,9 +89,6 @@ public:
TextBufferTextIterator GetTextDataAt(const til::point at, const Microsoft::Console::Types::Viewport limit) const;
// Text insertion functions
static void ConsumeGrapheme(std::wstring_view& chars) noexcept;
void WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state);
OutputCellIterator Write(const OutputCellIterator givenIt);
OutputCellIterator Write(const OutputCellIterator givenIt,
@@ -219,8 +216,6 @@ public:
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const;
private:
static wil::unique_virtualalloc_ptr<std::byte> _allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector<ROW>& rows);
void _UpdateSize();
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
til::point _GetPreviousFromCursor() const noexcept;

View File

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

View File

@@ -830,7 +830,7 @@ class ReflowTests
for (size_t bufferIndex{ 1 }; bufferIndex < testCase.buffers.size(); ++bufferIndex)
{
const auto& testBuffer{ til::at(testCase.buffers, bufferIndex) };
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.width, testBuffer.size.height));
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.X, testBuffer.size.Y));
auto newBuffer{ _textBufferByReflowingTextBuffer(*textBuffer, testBuffer.size) };

View File

@@ -153,8 +153,6 @@
<ItemGroup>
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs'" />
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug'" />
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs.Desktop'" />
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.UWPDesktop' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug.UWPDesktop'" />
</ItemGroup>
</Target>
@@ -163,8 +161,6 @@
<ItemGroup>
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs'" />
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug'" />
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs.Desktop'" />
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.UWPDesktop' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug.UWPDesktop'" />
</ItemGroup>
</Target>

View File

@@ -10,11 +10,9 @@
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization">
IgnorableNamespaces="uap mp rescap uap3">
<Identity
Name="WindowsTerminalDev"
@@ -25,14 +23,6 @@
<DisplayName>ms-resource:AppStoreNameDev</DisplayName>
<PublisherDisplayName>A Lone Developer</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
<!-- Older versions of Windows 10 respect this -->
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
<!-- Newer versions of Windows 10 plus all versions of Windows 11 respect this -->
<virtualization:RegistryWriteVirtualization>
<virtualization:ExcludedKeys>
<virtualization:ExcludedKey>HKEY_CURRENT_USER\Console\%%Startup</virtualization:ExcludedKey>
</virtualization:ExcludedKeys>
</virtualization:RegistryWriteVirtualization>
</Properties>
<Dependencies>
@@ -146,6 +136,5 @@
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources" />
</Capabilities>
</Package>

View File

@@ -12,10 +12,8 @@
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization">
IgnorableNamespaces="uap mp rescap uap3">
<Identity
Name="Microsoft.WindowsTerminalPreview"
@@ -26,14 +24,6 @@
<DisplayName>ms-resource:AppStoreNamePre</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
<!-- Older versions of Windows 10 respect this -->
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
<!-- Newer versions of Windows 10 plus all versions of Windows 11 respect this -->
<virtualization:RegistryWriteVirtualization>
<virtualization:ExcludedKeys>
<virtualization:ExcludedKey>HKEY_CURRENT_USER\Console\%%Startup</virtualization:ExcludedKey>
</virtualization:ExcludedKeys>
</virtualization:RegistryWriteVirtualization>
</Properties>
<Dependencies>
@@ -235,7 +225,6 @@
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources" />
</Capabilities>
<Extensions>

View File

@@ -12,10 +12,8 @@
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization">
IgnorableNamespaces="uap mp rescap uap3">
<Identity
Name="Microsoft.WindowsTerminal"
@@ -26,14 +24,6 @@
<DisplayName>ms-resource:AppStoreName</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
<!-- Older versions of Windows 10 respect this -->
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
<!-- Newer versions of Windows 10 plus all versions of Windows 11 respect this -->
<virtualization:RegistryWriteVirtualization>
<virtualization:ExcludedKeys>
<virtualization:ExcludedKey>HKEY_CURRENT_USER\Console\%%Startup</virtualization:ExcludedKey>
</virtualization:ExcludedKeys>
</virtualization:RegistryWriteVirtualization>
</Properties>
<Dependencies>
@@ -235,7 +225,6 @@
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources" />
</Capabilities>
<Extensions>

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ class JsonTestClass
public:
static Json::Value VerifyParseSucceeded(const std::string_view& content)
{
static const std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder{}.newCharReader() };
static const std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
Json::Value root;
std::string errs;
@@ -31,7 +31,7 @@ public:
static std::string toString(const Json::Value& json)
{
static const std::unique_ptr<Json::StreamWriter> writer{ Json::StreamWriterBuilder{}.newStreamWriter() };
static const std::unique_ptr<Json::StreamWriter> writer{ Json::StreamWriterBuilder::StreamWriterBuilder().newStreamWriter() };
std::stringstream s;
writer->write(json, &s);

View File

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

View File

@@ -38,9 +38,6 @@ namespace SettingsModelLocalTests
TEST_METHOD(LayerProfileProperties);
TEST_METHOD(LayerProfileIcon);
TEST_METHOD(LayerProfilesOnArray);
TEST_METHOD(ProfileWithEnvVars);
TEST_METHOD(ProfileWithEnvVarsSameNameDifferentCases);
TEST_METHOD(DuplicateProfileTest);
TEST_METHOD(TestGenGuidsForProfiles);
TEST_METHOD(TestCorrectOldDefaultShellPaths);
@@ -352,50 +349,6 @@ namespace SettingsModelLocalTests
VERIFY_ARE_NOT_EQUAL(settings->AllProfiles().GetAt(0).Guid(), settings->AllProfiles().GetAt(1).Guid());
}
void ProfileTests::ProfileWithEnvVars()
{
const std::string profileString{ R"({
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"environment": {
"VAR_1": "value1",
"VAR_2": "value2",
"VAR_3": "%VAR_3%;value3"
}
})" };
const auto profile = implementation::Profile::FromJson(VerifyParseSucceeded(profileString));
std::vector<IEnvironmentVariableMap> envVarMaps{};
envVarMaps.emplace_back(profile->EnvironmentVariables());
for (auto& envMap : envVarMaps)
{
VERIFY_ARE_EQUAL(static_cast<uint32_t>(3), envMap.Size());
VERIFY_ARE_EQUAL(L"value1", envMap.Lookup(L"VAR_1"));
VERIFY_ARE_EQUAL(L"value2", envMap.Lookup(L"VAR_2"));
VERIFY_ARE_EQUAL(L"%VAR_3%;value3", envMap.Lookup(L"VAR_3"));
}
}
void ProfileTests::ProfileWithEnvVarsSameNameDifferentCases()
{
const std::string userSettings{ R"({
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"environment": {
"FOO": "VALUE",
"Foo": "Value"
}
}
]
})" };
const auto settings = winrt::make_self<implementation::CascadiaSettings>(userSettings);
const auto warnings = settings->Warnings();
VERIFY_ARE_EQUAL(static_cast<uint32_t>(2), warnings.Size());
uint32_t index;
VERIFY_IS_TRUE(warnings.IndexOf(SettingsLoadWarnings::InvalidProfileEnvironmentVariables, index));
}
void ProfileTests::TestCorrectOldDefaultShellPaths()
{
static constexpr std::string_view inboxProfiles{ R"({

View File

@@ -134,7 +134,7 @@ namespace SettingsModelLocalTests
"font": {
"face": "Cascadia Mono",
"size": 12.0,
"size": 12,
"weight": "normal"
},
"padding": "8, 8, 8, 8",
@@ -165,13 +165,7 @@ namespace SettingsModelLocalTests
"historySize": 9001,
"closeOnExit": "graceful",
"experimental.retroTerminalEffect": false,
"environment":
{
"KEY_1": "VALUE_1",
"KEY_2": "%KEY_1%",
"KEY_3": "%PATH%"
}
"experimental.retroTerminalEffect": false
})" };
static constexpr std::string_view smallProfileString{ R"(
@@ -256,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
@@ -266,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
@@ -290,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" },
]
}
@@ -412,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": {
@@ -490,7 +478,7 @@ namespace SettingsModelLocalTests
"name": "Profile with legacy font settings",
"fontFace": "Cascadia Mono",
"fontSize": 12.0,
"fontSize": 12,
"fontWeight": "normal"
})" };
@@ -500,7 +488,7 @@ namespace SettingsModelLocalTests
"font": {
"face": "Cascadia Mono",
"size": 12.0,
"size": 12,
"weight": "normal"
}
})" };

View File

@@ -8,7 +8,7 @@
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into its own
TestHostApp will manually copy the output of this project into it's own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
@@ -20,7 +20,6 @@
<ConfigurationType>DynamicLibrary</ConfigurationType>
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
<!-- TerminalCppWinrt is not set intentionally -->
<TerminalMUX>true</TerminalMUX>
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
@@ -40,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" />
@@ -74,6 +72,8 @@
<ClCompile>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<!-- Required for JsonUtils.h. -->
<AdditionalOptions>%(AdditionalOptions) /Zc:twoPhase-</AdditionalOptions>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
@@ -98,4 +98,15 @@
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\common.build.tests.props" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<PropertyGroup>
<!-- From Microsoft.UI.Xaml.targets -->
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
</PropertyGroup>
<!-- We actually can just straight up reference MUX here, it's fine -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets')" />
</Project>

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ Author(s):
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
// This includes support libraries from the CRT, STL, WIL, and GSL
#include <LibraryIncludes.h>
#include "LibraryIncludes.h"
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
// SDK definition of this function, so the only fix is to undef it.
// from WinBase.h
@@ -28,9 +28,8 @@ Author(s):
#endif
#include <wil/cppwinrt.h>
#include <Unknwn.h>
#include <unknwn.h>
#include <hstring.h>
#include <shellapi.h>
#include <WexTestClass.h>
#include <json.h>
@@ -45,7 +44,6 @@ Author(s):
#include <Windows.Graphics.Imaging.Interop.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
#include <winrt/Windows.UI.ViewManagement.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ui.xaml.media.h>

View File

@@ -98,26 +98,6 @@ namespace TerminalAppLocalTests
}
}
}
void _logCommands(winrt::Windows::Foundation::Collections::IVector<Command> commands, const int indentation = 1)
{
if (indentation == 1)
{
Log::Comment((commands.Size() == 0) ? L"Commands:\n <none>" : L"Commands:");
}
for (const auto& cmd : commands)
{
Log::Comment(fmt::format(L"{0:>{1}}* {2}",
L"",
indentation,
cmd.Name())
.c_str());
if (cmd.HasNestedCommands())
{
_logCommandNames(cmd.NestedCommands(), indentation + 2);
}
}
}
};
void SettingsTests::TestIterateCommands()
@@ -184,15 +164,14 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
}
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
{
auto command = expandedCommands.GetAt(0);
VERIFY_ARE_EQUAL(L"iterable command profile0", command.Name());
auto command = expandedCommands.Lookup(L"iterable command profile0");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -210,8 +189,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(1);
VERIFY_ARE_EQUAL(L"iterable command profile1", command.Name());
auto command = expandedCommands.Lookup(L"iterable command profile1");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -229,8 +207,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(2);
VERIFY_ARE_EQUAL(L"iterable command profile2", command.Name());
auto command = expandedCommands.Lookup(L"iterable command profile2");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -310,16 +287,14 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
}
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
{
auto command = expandedCommands.GetAt(0);
VERIFY_ARE_EQUAL(L"Split pane, profile: profile0", command.Name());
auto command = expandedCommands.Lookup(L"Split pane, profile: profile0");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -337,9 +312,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(1);
VERIFY_ARE_EQUAL(L"Split pane, profile: profile1", command.Name());
auto command = expandedCommands.Lookup(L"Split pane, profile: profile1");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -357,9 +330,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(2);
VERIFY_ARE_EQUAL(L"Split pane, profile: profile2", command.Name());
auto command = expandedCommands.Lookup(L"Split pane, profile: profile2");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -441,16 +412,14 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
}
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
{
auto command = expandedCommands.GetAt(0);
VERIFY_ARE_EQUAL(L"iterable command profile0", command.Name());
auto command = expandedCommands.Lookup(L"iterable command profile0");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -468,9 +437,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(1);
VERIFY_ARE_EQUAL(L"iterable command profile1\"", command.Name());
auto command = expandedCommands.Lookup(L"iterable command profile1\"");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -488,9 +455,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(2);
VERIFY_ARE_EQUAL(L"iterable command profile2", command.Name());
auto command = expandedCommands.Lookup(L"iterable command profile2");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -562,15 +527,14 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
auto rootCommand = expandedCommands.GetAt(0);
auto rootCommand = expandedCommands.Lookup(L"Connect to ssh...");
VERIFY_IS_NOT_NULL(rootCommand);
VERIFY_ARE_EQUAL(L"Connect to ssh...", rootCommand.Name());
auto rootActionAndArgs = rootCommand.ActionAndArgs();
VERIFY_IS_NOT_NULL(rootActionAndArgs);
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
@@ -657,16 +621,14 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
auto grandparentCommand = expandedCommands.GetAt(0);
auto grandparentCommand = expandedCommands.Lookup(L"grandparent");
VERIFY_IS_NOT_NULL(grandparentCommand);
VERIFY_ARE_EQUAL(L"grandparent", grandparentCommand.Name());
auto grandparentActionAndArgs = grandparentCommand.ActionAndArgs();
VERIFY_IS_NOT_NULL(grandparentActionAndArgs);
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, grandparentActionAndArgs.Action());
@@ -782,22 +744,17 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
const std::vector<std::wstring> profileNames{ L"profile0", L"profile1", L"profile2" };
for (auto i = 0u; i < profileNames.size(); i++)
for (auto name : std::vector<std::wstring>({ L"profile0", L"profile1", L"profile2" }))
{
const auto& name{ profileNames[i] };
winrt::hstring commandName{ profileNames[i] + L"..." };
auto command = expandedCommands.GetAt(i);
VERIFY_ARE_EQUAL(commandName, command.Name());
winrt::hstring commandName{ name + L"..." };
auto command = expandedCommands.Lookup(commandName);
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -923,16 +880,14 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
auto rootCommand = expandedCommands.GetAt(0);
auto rootCommand = expandedCommands.Lookup(L"New Tab With Profile...");
VERIFY_IS_NOT_NULL(rootCommand);
VERIFY_ARE_EQUAL(L"New Tab With Profile...", rootCommand.Name());
auto rootActionAndArgs = rootCommand.ActionAndArgs();
VERIFY_IS_NOT_NULL(rootActionAndArgs);
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
@@ -1027,16 +982,13 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
auto rootCommand = expandedCommands.GetAt(0);
VERIFY_IS_NOT_NULL(rootCommand);
VERIFY_ARE_EQUAL(L"New Pane...", rootCommand.Name());
auto rootCommand = expandedCommands.Lookup(L"New Pane...");
VERIFY_IS_NOT_NULL(rootCommand);
auto rootActionAndArgs = rootCommand.ActionAndArgs();
VERIFY_IS_NOT_NULL(rootActionAndArgs);
@@ -1253,8 +1205,8 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile());
}
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommands(expandedCommands);
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
@@ -1263,9 +1215,7 @@ namespace TerminalAppLocalTests
// just easy tests to write.
{
auto command = expandedCommands.GetAt(0);
VERIFY_ARE_EQUAL(L"iterable command Campbell", command.Name());
auto command = expandedCommands.Lookup(L"iterable command Campbell");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -1283,9 +1233,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(1);
VERIFY_ARE_EQUAL(L"iterable command Campbell PowerShell", command.Name());
auto command = expandedCommands.Lookup(L"iterable command Campbell PowerShell");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);
@@ -1303,9 +1251,7 @@ namespace TerminalAppLocalTests
}
{
auto command = expandedCommands.GetAt(2);
VERIFY_ARE_EQUAL(L"iterable command Vintage", command.Name());
auto command = expandedCommands.Lookup(L"iterable command Vintage");
VERIFY_IS_NOT_NULL(command);
auto actionAndArgs = command.ActionAndArgs();
VERIFY_IS_NOT_NULL(actionAndArgs);

View File

@@ -4,13 +4,11 @@
#include "pch.h"
#include "../TerminalApp/TerminalPage.h"
#include "../TerminalApp/TerminalWindow.h"
#include "../TerminalApp/MinMaxCloseControl.h"
#include "../TerminalApp/TabRowControl.h"
#include "../TerminalApp/ShortcutActionDispatch.h"
#include "../TerminalApp/TerminalTab.h"
#include "../TerminalApp/CommandPalette.h"
#include "../TerminalApp/ContentManager.h"
#include "CppWinrtTailored.h"
using namespace Microsoft::Console;
@@ -112,8 +110,6 @@ namespace TerminalAppLocalTests
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
CascadiaSettings initialSettings);
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
winrt::com_ptr<winrt::TerminalApp::implementation::WindowProperties> _windowProperties;
winrt::com_ptr<winrt::TerminalApp::implementation::ContentManager> _contentManager;
};
template<typename TFunction>
@@ -198,14 +194,8 @@ namespace TerminalAppLocalTests
{
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_windowProperties = winrt::make_self<winrt::TerminalApp::implementation::WindowProperties>();
winrt::TerminalApp::WindowProperties props = *_windowProperties;
_contentManager = winrt::make_self<winrt::TerminalApp::implementation::ContentManager>();
winrt::TerminalApp::ContentManager contentManager = *_contentManager;
auto result = RunOnUIThread([&page, props, contentManager]() {
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>(props, contentManager);
auto result = RunOnUIThread([&page]() {
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>();
VERIFY_IS_NOT_NULL(page);
});
VERIFY_SUCCEEDED(result);
@@ -249,13 +239,9 @@ namespace TerminalAppLocalTests
// it's weird.
winrt::TerminalApp::TerminalPage projectedPage{ nullptr };
_windowProperties = winrt::make_self<winrt::TerminalApp::implementation::WindowProperties>();
winrt::TerminalApp::WindowProperties props = *_windowProperties;
_contentManager = winrt::make_self<winrt::TerminalApp::implementation::ContentManager>();
winrt::TerminalApp::ContentManager contentManager = *_contentManager;
Log::Comment(NoThrowString().Format(L"Construct the TerminalPage"));
auto result = RunOnUIThread([&projectedPage, &page, initialSettings, props, contentManager]() {
projectedPage = winrt::TerminalApp::TerminalPage(props, contentManager);
auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() {
projectedPage = winrt::TerminalApp::TerminalPage();
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
page->_settings = initialSettings;
});
@@ -1102,7 +1088,7 @@ namespace TerminalAppLocalTests
// If you don't do this, the palette will just stay open, and the
// next time we call _HandleNextTab, we'll continue traversing the
// MRU list, instead of just hoping one entry.
page->LoadCommandPalette().Visibility(Visibility::Collapsed);
page->CommandPalette().Visibility(Visibility::Collapsed);
});
TestOnUIThread([&page]() {
@@ -1123,7 +1109,7 @@ namespace TerminalAppLocalTests
// If you don't do this, the palette will just stay open, and the
// next time we call _HandleNextTab, we'll continue traversing the
// MRU list, instead of just hoping one entry.
page->LoadCommandPalette().Visibility(Visibility::Collapsed);
page->CommandPalette().Visibility(Visibility::Collapsed);
});
TestOnUIThread([&page]() {
@@ -1239,7 +1225,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title());
});
const auto palette = winrt::get_self<winrt::TerminalApp::implementation::CommandPalette>(page->LoadCommandPalette());
const auto palette = winrt::get_self<winrt::TerminalApp::implementation::CommandPalette>(page->CommandPalette());
VERIFY_ARE_EQUAL(winrt::TerminalApp::implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode");
// At this point, the contents of the command palette's _mruTabs list is
@@ -1256,16 +1242,14 @@ namespace TerminalAppLocalTests
END_TEST_METHOD_PROPERTIES()
auto page = _commonSetup();
page->RenameWindowRequested([&page, this](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) {
page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) {
// In the real terminal, this would bounce up to the monarch and
// come back down. Instead, immediately call back and set the name.
//
// This replicates how TerminalWindow works
_windowProperties->WindowName(args.ProposedName());
page->WindowName(args.ProposedName());
});
auto windowNameChanged = false;
_windowProperties->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable {
page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable {
if (args.PropertyName() == L"WindowNameForDisplay")
{
windowNameChanged = true;
@@ -1276,7 +1260,7 @@ namespace TerminalAppLocalTests
page->_RequestWindowRename(winrt::hstring{ L"Foo" });
});
TestOnUIThread([&]() {
VERIFY_ARE_EQUAL(L"Foo", page->WindowProperties().WindowName());
VERIFY_ARE_EQUAL(L"Foo", page->_WindowName);
VERIFY_IS_TRUE(windowNameChanged,
L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed");
});

View File

@@ -8,7 +8,7 @@
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into its own
TestHostApp will manually copy the output of this project into it's own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
@@ -23,7 +23,7 @@
<PropertyGroup Label="NuGet Dependencies">
<!-- TerminalCppWinrt is intentionally not set -->
<TerminalMUX>true</TerminalMUX>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
@@ -86,4 +86,15 @@
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\common.build.tests.props" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<PropertyGroup>
<!-- From Microsoft.UI.Xaml.targets -->
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
</PropertyGroup>
<!-- We actually can just straight up reference MUX here, it's fine -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets')" />
</Project>

View File

@@ -14,9 +14,6 @@
<UseWmXml>true</UseWmXml>
<ConfigurationType>Application</ConfigurationType>
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
<EnableHybridCRT>false</EnableHybridCRT> <!-- C++/CLI projects can't deal -->
<TerminalMUX>true</TerminalMUX>
<!--
These two properties are very important!
@@ -128,6 +125,8 @@
</Reference>
</ItemGroup>
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="$(OpenConsoleDir)\src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)\src\common.nugetversions.targets" />

View File

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

View File

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

View File

@@ -60,4 +60,4 @@
<AdditionalDependencies>Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
</Project>
</Project>

View File

@@ -14,11 +14,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
CommandlineArgs(const winrt::array_view<const winrt::hstring>& args,
winrt::hstring currentDirectory,
const uint32_t showWindowCommand) :
winrt::hstring currentDirectory) :
_args{ args.begin(), args.end() },
_cwd{ currentDirectory },
_ShowWindowCommand{ showWindowCommand }
_cwd{ currentDirectory }
{
}
@@ -27,8 +25,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void Commandline(const winrt::array_view<const winrt::hstring>& value);
winrt::com_array<winrt::hstring> Commandline();
WINRT_PROPERTY(uint32_t, ShowWindowCommand, SW_NORMAL); // SW_NORMAL is 1, 0 is SW_HIDE
private:
winrt::com_array<winrt::hstring> _args;
winrt::hstring _cwd;

View File

@@ -10,7 +10,6 @@
#include "ProposeCommandlineResult.h"
#include "Monarch.g.cpp"
#include "WindowRequestedArgs.g.cpp"
#include "../../types/inc/utils.hpp"
using namespace winrt;
@@ -659,13 +658,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
if (targetWindow == WindowingBehaviorUseNone)
{
// In this case, the targetWindow was UseNone, which means that we
// want to make a message box, but otherwise not make a Terminal
// window.
return winrt::make<Remoting::implementation::ProposeCommandlineResult>(false);
}
// If there's a valid ID returned, then let's try and find the peasant
// that goes with it. Alternatively, if we were given a magic windowing
// constant, we can use that to look up an appropriate peasant.
@@ -695,11 +687,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
case WindowingBehaviorUseName:
windowID = _lookupPeasantIdForName(targetWindowName);
break;
case WindowingBehaviorUseNone:
// This should be impossible. The if statement above should have
// prevented WindowingBehaviorUseNone from falling in here.
// Explode, because this is a programming error.
THROW_HR(E_UNEXPECTED);
default:
windowID = ::base::saturated_cast<uint64_t>(targetWindow);
break;
@@ -737,8 +724,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
result->WindowName(targetWindowName);
result->ShouldCreateWindow(true);
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
// If this fails, it'll be logged in the following
// TraceLoggingWrite statement, with succeeded=false
}
@@ -774,9 +759,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true) };
result->Id(windowID);
result->WindowName(targetWindowName);
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
return *result;
}
}
@@ -791,9 +773,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// In this case, no usable ID was provided. Return { true, nullopt }
auto result = winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true);
result->WindowName(targetWindowName);
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
return *result;
}
@@ -1055,95 +1034,4 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return winrt::single_threaded_vector(std::move(vec));
}
void Monarch::RequestMoveContent(winrt::hstring window,
winrt::hstring content,
uint32_t tabIndex,
const Windows::Foundation::IReference<Windows::Foundation::Rect>& windowBounds)
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_Requested",
TraceLoggingWideString(window.c_str(), "window", "The name of the window we tried to move to"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
uint64_t windowId = _lookupPeasantIdForName(window);
if (windowId == 0)
{
// Try the name as an integer ID
uint32_t temp;
if (!Utils::StringToUint(window.c_str(), temp))
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_FailedToParseId",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
windowId = temp;
}
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
auto request = winrt::make_self<implementation::AttachRequest>(content, tabIndex);
targetPeasant.AttachContentToWindow(*request);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_Completed",
TraceLoggingInt64(windowId, "windowId", "The ID of the peasant which we sent the content to"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_NoWindow",
TraceLoggingInt64(windowId, "windowId", "We could not find a peasant with this ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// In the case where window couldn't be found, then create a window
// for that name / ID.
//
// Don't let the window literally be named "-1", because that's silly
auto request = winrt::make_self<implementation::WindowRequestedArgs>(window == L"-1" ? L"" : window,
content,
windowBounds);
_RequestNewWindowHandlers(*this, *request);
}
}
// Very similar to the above. Someone came and told us that they were the target of a drag/drop, and they know who started it.
// We will go tell the person who started it that they should send that target the content which was dragged.
void Monarch::RequestSendContent(const Remoting::RequestReceiveContentArgs& args)
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_Requested",
TraceLoggingUInt64(args.SourceWindow(), "source", "The window which started the drag"),
TraceLoggingUInt64(args.TargetWindow(), "target", "The window which was the target of the drop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
if (auto senderPeasant{ _getPeasant(args.SourceWindow()) })
{
senderPeasant.SendContent(args);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_Completed",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
// We couldn't find the peasant that started the drag. Well that
// sure is weird, but that would indicate that the sender closed
// after starting the drag. No matter. We can just do nothing.
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_NoWindow",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
}

View File

@@ -6,7 +6,6 @@
#include "Monarch.g.h"
#include "Peasant.h"
#include "WindowActivatedArgs.h"
#include "WindowRequestedArgs.g.h"
#include <atomic>
// We sure different GUIDs here depending on whether we're running a Release,
@@ -39,38 +38,6 @@ namespace RemotingUnitTests
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct WindowRequestedArgs : public WindowRequestedArgsT<WindowRequestedArgs>
{
public:
WindowRequestedArgs(const Remoting::ProposeCommandlineResult& windowInfo, const Remoting::CommandlineArgs& command) :
_Id{ windowInfo.Id() ? windowInfo.Id().Value() : 0 }, // We'll use 0 as a sentinel, since no window will ever get to have that ID
_WindowName{ windowInfo.WindowName() },
_args{ command.Commandline() },
_CurrentDirectory{ command.CurrentDirectory() },
_ShowWindowCommand{ command.ShowWindowCommand() } {};
WindowRequestedArgs(const winrt::hstring& window, const winrt::hstring& content, const Windows::Foundation::IReference<Windows::Foundation::Rect>& bounds) :
_Id{ 0u },
_WindowName{ window },
_args{},
_CurrentDirectory{},
_Content{ content },
_InitialBounds{ bounds } {};
void Commandline(const winrt::array_view<const winrt::hstring>& value) { _args = { value.begin(), value.end() }; };
winrt::com_array<winrt::hstring> Commandline() { return winrt::com_array<winrt::hstring>{ _args.begin(), _args.end() }; }
WINRT_PROPERTY(uint64_t, Id);
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, CurrentDirectory);
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(uint32_t, ShowWindowCommand, SW_NORMAL);
WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Rect>, InitialBounds);
private:
winrt::com_array<winrt::hstring> _args;
};
struct Monarch : public MonarchT<Monarch>
{
Monarch();
@@ -93,9 +60,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
void RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex, const Windows::Foundation::IReference<Windows::Foundation::Rect>& windowBounds);
void RequestSendContent(const Remoting::RequestReceiveContentArgs& args);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
@@ -103,8 +67,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs);
TYPED_EVENT(RequestNewWindow, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs);
private:
uint64_t _ourPID;
@@ -229,5 +191,4 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
{
BASIC_FACTORY(Monarch);
BASIC_FACTORY(WindowRequestedArgs);
}

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