mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-09 15:51:05 +00:00
Compare commits
93 Commits
dev/lhecke
...
dev/duhowe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
783b361b11 | ||
|
|
338c5047d7 | ||
|
|
33589cd8db | ||
|
|
ad51b22f44 | ||
|
|
ec5d246b35 | ||
|
|
043d5cd484 | ||
|
|
b780d8ab7e | ||
|
|
6e451a2d4b | ||
|
|
99042d2f0c | ||
|
|
30dbd3b554 | ||
|
|
94e74d22c6 | ||
|
|
badc00e83b | ||
|
|
e7796e7db3 | ||
|
|
de0f702c74 | ||
|
|
abf5d9423a | ||
|
|
4ff38c260f | ||
|
|
fefee50757 | ||
|
|
7c031a2893 | ||
|
|
88def9ddcd | ||
|
|
bb5f56e704 | ||
|
|
e5d7fd0230 | ||
|
|
7c1edbdb5f | ||
|
|
2dfa3da199 | ||
|
|
4db5e0eefd | ||
|
|
a6a0e44088 | ||
|
|
6b29ef51e3 | ||
|
|
ec434e3fba | ||
|
|
9c8058c326 | ||
|
|
bf25595961 | ||
|
|
9654fc6afe | ||
|
|
3f27765861 | ||
|
|
0ec73b1a6e | ||
|
|
d3ec47a7fc | ||
|
|
78da9bd965 | ||
|
|
5e9f223a6c | ||
|
|
23580749a4 | ||
|
|
e3ff44bb82 | ||
|
|
f30cbef34d | ||
|
|
d3a18b9041 | ||
|
|
bef234081a | ||
|
|
71c35cf24c | ||
|
|
71efdcb21b | ||
|
|
ec91be5995 | ||
|
|
5dda50767b | ||
|
|
b70fd5e9c6 | ||
|
|
151da764ef | ||
|
|
438571bd7f | ||
|
|
ef96e225da | ||
|
|
01868978b3 | ||
|
|
26d35c3ac5 | ||
|
|
c669afe2a0 | ||
|
|
ce30e7c89c | ||
|
|
bcca7aac1b | ||
|
|
add1632d63 | ||
|
|
a2bb3136bb | ||
|
|
dccc1f4240 | ||
|
|
86c30bdaa2 | ||
|
|
bc48eda022 | ||
|
|
29895e1c2d | ||
|
|
5f71cf3e94 | ||
|
|
04edb112ea | ||
|
|
9c10575c73 | ||
|
|
a3ac337d88 | ||
|
|
5d2fa4782f | ||
|
|
7e46fa35f7 | ||
|
|
63bfdb2e1e | ||
|
|
5575187b26 | ||
|
|
48a6d92255 | ||
|
|
e727aaf679 | ||
|
|
98146c9d1b | ||
|
|
e6ac014fc8 | ||
|
|
0d47c862c2 | ||
|
|
a39ac598cd | ||
|
|
b08dc61a9c | ||
|
|
ba6f1e905d | ||
|
|
bc452c61dc | ||
|
|
204794f9f3 | ||
|
|
4902b342ef | ||
|
|
03aa8a6231 | ||
|
|
e75a4be4fe | ||
|
|
da99d892f4 | ||
|
|
f5898886be | ||
|
|
fe65d9ac8f | ||
|
|
6c192d15be | ||
|
|
da182e6c59 | ||
|
|
521a300c17 | ||
|
|
10fb5448cc | ||
|
|
a24afcd1e6 | ||
|
|
92f9ff948b | ||
|
|
1f26982238 | ||
|
|
3497f94741 | ||
|
|
cec302a2d2 | ||
|
|
f53608df26 |
8
.github/actions/spelling/allow/allow.txt
vendored
8
.github/actions/spelling/allow/allow.txt
vendored
@@ -17,6 +17,8 @@ CMMI
|
||||
copyable
|
||||
Counterintuitively
|
||||
CtrlDToClose
|
||||
CVS
|
||||
CUI
|
||||
cybersecurity
|
||||
dalet
|
||||
Dcs
|
||||
@@ -80,19 +82,24 @@ noreply
|
||||
ogonek
|
||||
ok'd
|
||||
overlined
|
||||
perlw
|
||||
pipeline
|
||||
postmodern
|
||||
Powerline
|
||||
powerline
|
||||
ptys
|
||||
pwshw
|
||||
qof
|
||||
qps
|
||||
Remappings
|
||||
Retargets
|
||||
rclt
|
||||
reimplementation
|
||||
reserialization
|
||||
reserialize
|
||||
reserializes
|
||||
rlig
|
||||
rubyw
|
||||
runtimes
|
||||
servicebus
|
||||
shcha
|
||||
@@ -113,6 +120,7 @@ toolset
|
||||
truthiness
|
||||
tshe
|
||||
ubuntu
|
||||
UEFI
|
||||
uiatextrange
|
||||
UIs
|
||||
und
|
||||
|
||||
16
.github/actions/spelling/allow/apis.txt
vendored
16
.github/actions/spelling/allow/apis.txt
vendored
@@ -1,9 +1,12 @@
|
||||
aalt
|
||||
abvm
|
||||
ACCEPTFILES
|
||||
ACCESSDENIED
|
||||
acl
|
||||
aclapi
|
||||
alignas
|
||||
alignof
|
||||
allocconsolewithoptions
|
||||
APPLYTOSUBMENUS
|
||||
appxrecipe
|
||||
bitfield
|
||||
@@ -21,7 +24,9 @@ COLORPROPERTY
|
||||
colspan
|
||||
COMDLG
|
||||
commandlinetoargv
|
||||
commoncontrols
|
||||
comparand
|
||||
COPYFROMRESOURCE
|
||||
cstdint
|
||||
CXICON
|
||||
CYICON
|
||||
@@ -33,6 +38,7 @@ delayimp
|
||||
DERR
|
||||
dlldata
|
||||
DNE
|
||||
dnom
|
||||
DONTADDTORECENT
|
||||
DWMSBT
|
||||
DWMWA
|
||||
@@ -41,10 +47,12 @@ endfor
|
||||
ENDSESSION
|
||||
enumset
|
||||
environstrings
|
||||
EXACTSIZEONLY
|
||||
EXPCMDFLAGS
|
||||
EXPCMDSTATE
|
||||
filetime
|
||||
FILTERSPEC
|
||||
fina
|
||||
FORCEFILESYSTEM
|
||||
FORCEMINIMIZE
|
||||
frac
|
||||
@@ -58,6 +66,7 @@ Hashtable
|
||||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
hinternet
|
||||
HIGHQUALITYSCALE
|
||||
HINTERNET
|
||||
hotkeys
|
||||
href
|
||||
@@ -74,6 +83,7 @@ IBox
|
||||
IClass
|
||||
IComparable
|
||||
IComparer
|
||||
ICONINFO
|
||||
IConnection
|
||||
ICustom
|
||||
IDialog
|
||||
@@ -83,6 +93,7 @@ IExplorer
|
||||
IFACEMETHOD
|
||||
IFile
|
||||
IGraphics
|
||||
IImage
|
||||
IInheritable
|
||||
IMap
|
||||
IMonarch
|
||||
@@ -113,6 +124,7 @@ LSHIFT
|
||||
LTGRAY
|
||||
MAINWINDOW
|
||||
MAXIMIZEBOX
|
||||
medi
|
||||
memchr
|
||||
memicmp
|
||||
MENUCOMMAND
|
||||
@@ -143,6 +155,7 @@ NOTIFYBYPOS
|
||||
NOTIFYICON
|
||||
NOTIFYICONDATA
|
||||
ntprivapi
|
||||
numr
|
||||
oaidl
|
||||
ocidl
|
||||
ODR
|
||||
@@ -156,6 +169,7 @@ OUTLINETEXTMETRICW
|
||||
overridable
|
||||
PACL
|
||||
PAGESCROLL
|
||||
PALLOC
|
||||
PATINVERT
|
||||
PEXPLICIT
|
||||
PICKFOLDERS
|
||||
@@ -167,9 +181,11 @@ REGCLS
|
||||
RETURNCMD
|
||||
rfind
|
||||
RLO
|
||||
rnrn
|
||||
ROOTOWNER
|
||||
roundf
|
||||
RSHIFT
|
||||
rvrn
|
||||
SACL
|
||||
schandle
|
||||
SEH
|
||||
|
||||
4
.github/actions/spelling/allow/microsoft.txt
vendored
4
.github/actions/spelling/allow/microsoft.txt
vendored
@@ -20,6 +20,7 @@ cpptools
|
||||
cppvsdbg
|
||||
CPRs
|
||||
cryptbase
|
||||
cscript
|
||||
DACL
|
||||
DACLs
|
||||
defaultlib
|
||||
@@ -45,6 +46,7 @@ MSAA
|
||||
msixbundle
|
||||
MSVC
|
||||
MSVCP
|
||||
mtu
|
||||
muxc
|
||||
netcore
|
||||
Onefuzz
|
||||
@@ -88,8 +90,10 @@ Virtualization
|
||||
visualstudio
|
||||
vscode
|
||||
VSTHRD
|
||||
WINBASEAPI
|
||||
winsdkver
|
||||
wlk
|
||||
wscript
|
||||
wslpath
|
||||
wtl
|
||||
wtt
|
||||
|
||||
1
.github/actions/spelling/excludes.txt
vendored
1
.github/actions/spelling/excludes.txt
vendored
@@ -115,6 +115,7 @@
|
||||
^src/terminal/parser/ut_parser/Base64Test.cpp$
|
||||
^src/terminal/parser/ut_parser/run\.bat$
|
||||
^src/tools/benchcat
|
||||
^src/tools/ConsoleBench
|
||||
^src/tools/integrity/dirs$
|
||||
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
|
||||
^src/tools/RenderingTests/main\.cpp$
|
||||
|
||||
24
.github/actions/spelling/expect/expect.txt
vendored
24
.github/actions/spelling/expect/expect.txt
vendored
@@ -19,6 +19,7 @@ AFew
|
||||
AFill
|
||||
AFX
|
||||
AHelper
|
||||
ahicon
|
||||
ahz
|
||||
AImpl
|
||||
AInplace
|
||||
@@ -183,6 +184,7 @@ chh
|
||||
chshdng
|
||||
CHT
|
||||
Cic
|
||||
CLASSSTRING
|
||||
CLE
|
||||
cleartype
|
||||
CLICKACTIVE
|
||||
@@ -319,6 +321,7 @@ ctlseqs
|
||||
CTRLEVENT
|
||||
CTRLFREQUENCY
|
||||
CTRLKEYSHORTCUTS
|
||||
Ctrls
|
||||
CTRLVOLUME
|
||||
Ctxt
|
||||
CUF
|
||||
@@ -401,6 +404,7 @@ DECECM
|
||||
DECEKBD
|
||||
DECERA
|
||||
DECFI
|
||||
DECFNK
|
||||
DECFRA
|
||||
DECIC
|
||||
DECID
|
||||
@@ -426,6 +430,7 @@ DECRQM
|
||||
DECRQPSR
|
||||
DECRQSS
|
||||
DECRQTSR
|
||||
DECRQUPSS
|
||||
DECRSPS
|
||||
decrst
|
||||
DECSACE
|
||||
@@ -443,10 +448,12 @@ DECSLPP
|
||||
DECSLRM
|
||||
DECSMKR
|
||||
DECSR
|
||||
DECST
|
||||
DECSTBM
|
||||
DECSTGLT
|
||||
DECSTR
|
||||
DECSWL
|
||||
DECSWT
|
||||
DECTABSR
|
||||
DECTCEM
|
||||
DECXCPR
|
||||
@@ -481,6 +488,7 @@ directio
|
||||
DIRECTX
|
||||
DISABLEDELAYEDEXPANSION
|
||||
DISABLENOSCROLL
|
||||
DISPATCHNOTIFY
|
||||
DISPLAYATTRIBUTE
|
||||
DISPLAYATTRIBUTEPROPERTY
|
||||
DISPLAYCHANGE
|
||||
@@ -838,6 +846,9 @@ IGNORELANGUAGE
|
||||
IHosted
|
||||
iid
|
||||
IIo
|
||||
ILC
|
||||
ILCo
|
||||
ILD
|
||||
ime
|
||||
IMPEXP
|
||||
inbox
|
||||
@@ -1111,8 +1122,8 @@ msix
|
||||
msrc
|
||||
MSVCRTD
|
||||
MTSM
|
||||
munged
|
||||
munges
|
||||
Munged
|
||||
murmurhash
|
||||
muxes
|
||||
myapplet
|
||||
@@ -1335,11 +1346,14 @@ pgomgr
|
||||
PGONu
|
||||
pguid
|
||||
phhook
|
||||
phico
|
||||
phicon
|
||||
phwnd
|
||||
pidl
|
||||
PIDLIST
|
||||
pids
|
||||
pii
|
||||
piml
|
||||
pinvoke
|
||||
pipename
|
||||
pipestr
|
||||
@@ -1671,6 +1685,8 @@ slpit
|
||||
SManifest
|
||||
SMARTQUOTE
|
||||
SMTO
|
||||
snapcx
|
||||
snapcy
|
||||
SOLIDBOX
|
||||
Solutiondir
|
||||
somefile
|
||||
@@ -1868,7 +1884,12 @@ uiautomationcore
|
||||
uielem
|
||||
UIELEMENTENABLEDONLY
|
||||
UINTs
|
||||
ul
|
||||
ulcch
|
||||
uld
|
||||
uldb
|
||||
uldash
|
||||
ulwave
|
||||
Unadvise
|
||||
unattend
|
||||
UNCPRIORITY
|
||||
@@ -1891,6 +1912,7 @@ UPDATEDISPLAY
|
||||
UPDOWN
|
||||
UPKEY
|
||||
UPSS
|
||||
upss
|
||||
uregex
|
||||
URegular
|
||||
usebackq
|
||||
|
||||
473
OpenConsole.sln
473
OpenConsole.sln
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- template: steps-ensure-nuget-version.yml
|
||||
|
||||
- task: NuGetAuthenticate@0
|
||||
- task: NuGetAuthenticate@1
|
||||
inputs:
|
||||
nuGetServiceConnections: 'Terminal Public Artifact Feed'
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ jobs:
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run PGO Tests'
|
||||
inputs:
|
||||
pwsh: true
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: >-
|
||||
|
||||
@@ -44,6 +44,7 @@ jobs:
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run Unit Tests'
|
||||
inputs:
|
||||
pwsh: true
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(OutputBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' -Root "$(Terminal.BinDir)"
|
||||
@@ -52,6 +53,7 @@ jobs:
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run Feature Tests'
|
||||
inputs:
|
||||
pwsh: true
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(OutputBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' -Root "$(Terminal.BinDir)"
|
||||
|
||||
@@ -78,6 +78,9 @@ extends:
|
||||
cloudvault: # https://aka.ms/obpipelines/cloudvault
|
||||
enabled: false
|
||||
globalSdl: # https://aka.ms/obpipelines/sdl
|
||||
asyncSdl:
|
||||
enabled: true
|
||||
tsaOptionsFile: 'build/config/tsa.json'
|
||||
tsa:
|
||||
enabled: true
|
||||
configFile: '$(Build.SourcesDirectory)\build\config\tsa.json'
|
||||
@@ -247,7 +250,7 @@ extends:
|
||||
|
||||
- stage: Publish
|
||||
displayName: Publish
|
||||
dependsOn: [Build, Package]
|
||||
dependsOn: [Build]
|
||||
jobs:
|
||||
- template: ./build/pipelines/templates-v2/job-publish-symbols.yml@self
|
||||
parameters:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
steps:
|
||||
- template: steps-ensure-nuget-version.yml
|
||||
|
||||
- task: NuGetAuthenticate@0
|
||||
- task: NuGetAuthenticate@1
|
||||
|
||||
- script: |-
|
||||
echo ##vso[task.setvariable variable=NUGET_RESTORE_MSBUILD_ARGS]/p:Platform=$(BuildPlatform)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<IntermediateOutputPath>$(SolutionDir)obj\$(Configuration)\GenerateFeatureFlags\</IntermediateOutputPath>
|
||||
<OpenConsoleCommonOutDir>$(SolutionDir)bin\$(Configuration)\</OpenConsoleCommonOutDir>
|
||||
|
||||
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Canary'">Canary</_WTBrandingName>
|
||||
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Preview'">Preview</_WTBrandingName>
|
||||
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Release'">Release</_WTBrandingName>
|
||||
<_WTBrandingName Condition="'$(_WTBrandingName)'==''">Dev</_WTBrandingName>
|
||||
|
||||
@@ -16,22 +16,48 @@ Param(
|
||||
# Find test DLLs based on the provided root, match pattern, and recursion
|
||||
$testDlls = Get-ChildItem -Path $Root -Recurse -Filter $MatchPattern
|
||||
|
||||
$args = @()
|
||||
$teArgs = @()
|
||||
|
||||
# Check if the LogPath parameter is provided and enable WTT logging
|
||||
if ($LogPath) {
|
||||
$args += '/enablewttlogging'
|
||||
$args += '/appendwttlogging'
|
||||
$args += "/logFile:$LogPath"
|
||||
$teArgs += '/enablewttlogging'
|
||||
$teArgs += '/appendwttlogging'
|
||||
$teArgs += "/logFile:$LogPath"
|
||||
Write-Host "WTT Logging Enabled"
|
||||
}
|
||||
|
||||
# Invoke the te.exe executable with arguments and test DLLs
|
||||
& "$Root\te.exe" $args $testDlls.FullName $AdditionalTaefArguments
|
||||
$rootTe = "$Root\te.exe"
|
||||
|
||||
# Check the exit code of the te.exe process and exit accordingly
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Exit $LASTEXITCODE
|
||||
# Some of our test fixtures depend on resources.pri in the same folder as the .exe hosting them.
|
||||
# Unfortunately, that means that we need to run the te.exe *next to* each test DLL we discover.
|
||||
# This code establishes a mapping from te.exe to test DLL (or DLLs)
|
||||
$testDllTaefGroups = $testDlls | % {
|
||||
$localTe = Get-Item (Join-Path (Split-Path $_ -Parent) "te.exe") -EA:Ignore
|
||||
If ($null -eq $localTe) {
|
||||
$finalTePath = $rootTe
|
||||
} Else {
|
||||
$finalTePath = $localTe.FullName
|
||||
}
|
||||
[PSCustomObject]@{
|
||||
TePath = $finalTePath;
|
||||
TestDll = $_;
|
||||
}
|
||||
}
|
||||
|
||||
# Invoke the te.exe executables with arguments and test DLLs
|
||||
$anyFailed = $false
|
||||
$testDllTaefGroups | Group-Object TePath | % {
|
||||
$te = $_.Group[0].TePath
|
||||
$dlls = $_.Group.TestDll
|
||||
Write-Verbose "Running $te (for $($dlls.Name))"
|
||||
& $te $teArgs $dlls.FullName $AdditionalTaefArguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$anyFailed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($anyFailed) {
|
||||
Exit 1
|
||||
}
|
||||
|
||||
Exit 0
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<!-- This file is read by XES, which we use in our Release builds. -->
|
||||
<PropertyGroup Label="Version">
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2023</XesBaseYearForStoreVersion>
|
||||
<XesBaseYearForStoreVersion>2024</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>20</VersionMinor>
|
||||
<VersionMinor>21</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" />
|
||||
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.230824.2" targetFramework="native" developmentDependency="true" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.240122.1" targetFramework="native" developmentDependency="true" />
|
||||
|
||||
<!-- Managed packages -->
|
||||
<package id="Appium.WebDriver" version="3.0.0.2" targetFramework="net45" />
|
||||
|
||||
@@ -2782,15 +2782,15 @@
|
||||
"description": "When set to true, marks added to the buffer via the addMark action will appear on the scrollbar.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental.repositionCursorWithMouse": {
|
||||
"default": false,
|
||||
"description": "When set to true, you can move the text cursor by clicking with the mouse on the current commandline. This is an experimental feature - there are lots of edge cases where this will not work as expected.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental.pixelShaderPath": {
|
||||
"description": "Use to set a path to a pixel shader to use with the Terminal. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.",
|
||||
"type": "string"
|
||||
},
|
||||
"useAtlasEngine": {
|
||||
"description": "Windows Terminal 1.16 and later ship with a new, performant text renderer. Set this to false to revert back to the old text renderer.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"fontFace": {
|
||||
"default": "Cascadia Mono",
|
||||
"description": "[deprecated] Define 'face' within the 'font' object instead.",
|
||||
|
||||
396
doc/specs/#7335 - Console Allocation Policy.md
Normal file
396
doc/specs/#7335 - Console Allocation Policy.md
Normal file
@@ -0,0 +1,396 @@
|
||||
---
|
||||
author: Dustin Howett @DHowett <duhowett@microsoft.com>
|
||||
created on: 2020-08-16
|
||||
last updated: 2023-12-12
|
||||
issue id: "#7335"
|
||||
---
|
||||
|
||||
# Console Allocation Policy
|
||||
|
||||
## Abstract
|
||||
|
||||
Due to the design of the console subsystem on Windows as it has existed since Windows 95, every application that is
|
||||
stamped with the `IMAGE_SUBSYSTEM_WINDOWS_CUI` subsystem in its PE header will be allocated a console by kernel32.
|
||||
|
||||
Any application that is stamped `IMAGE_SUBSYSTEM_WINDOWS_GUI` will not automatically be allocated a console.
|
||||
|
||||
This has worked fine for many years: when you double-click a console application in your GUI shell, it is allocated a
|
||||
console. When you run a GUI application from your console shell, it is **not** allocated a console. The shell will
|
||||
**not** wait for it to exit before returning you to a prompt.
|
||||
|
||||
There is a large class of applications that do not fit neatly into this mold. Take Python, Ruby, Perl, Lua, or even
|
||||
VBScript: These languages are not relegated to running in a console session; they can be used to write fully-fledged GUI
|
||||
applications like any other language.
|
||||
|
||||
Because their interpreters are console subsystem applications, however, any user double-clicking a shortcut to a Python
|
||||
or Perl application will be presented with a console window that the language runtime may choose to garbage collect, or
|
||||
may choose not to.
|
||||
|
||||
If the runtime chooses to hide the window, there will still be a brief period during which that window is visible. It is
|
||||
inescapable.
|
||||
|
||||
Likewise, any user running that GUI application from a console shell will see their shell hang until the application
|
||||
terminates.
|
||||
|
||||
All of these scripting languages worked around this by shipping two binaries each, identical in every way expect in
|
||||
their subsystem fields. python/pythonw, perl/perlw, ruby/rubyw, wscript/cscript.
|
||||
|
||||
PowerShell[^1] is waiting to deal with this problem because they don't necessarily want to ship a `pwshw.exe` for all
|
||||
of their GUI-only authors. Every additional `*w` version of an application is an additional maintenance burden and
|
||||
source of cognitive overhead[^2] for users.
|
||||
|
||||
On the other side, you have mostly-GUI applications that want to print output to a console **if there is one
|
||||
connected**.
|
||||
|
||||
These applications are still primarily GUI-driven, but they might support arguments like `/?` or `--help`. They only
|
||||
need a console when they need to print out some text. Sometimes they'll allocate their own console (which opens a new
|
||||
window) to display in, and sometimes they'll reattach to the originating console. VSCode does the latter, and so when
|
||||
you run `code` from CMD, and then `exit` CMD, your console window sticks around because VSCode is still attached to it.
|
||||
It will never print anything, and your only option is to close it.
|
||||
|
||||
There's another risk in reattaching, too. Given that the shell decides whether to wait based on the subsystem
|
||||
field, GUI subsystem applications that reattach to their owning consoles *just to print some text* end up stomping on
|
||||
the output of any shell that doesn't wait for them:
|
||||
|
||||
```
|
||||
C:\> application --help
|
||||
|
||||
application - the interesting application
|
||||
C:\> Usage: application [OPTIONS] ...
|
||||
```
|
||||
|
||||
> _(the prompt is interleaved with the output)_
|
||||
|
||||
## Solution Design
|
||||
|
||||
I propose that we introduce a fusion manifest field, **consoleAllocationPolicy**, with the following values:
|
||||
|
||||
* _absent_
|
||||
* `detached`
|
||||
|
||||
This field allows an application to disable the automatic allocation of a console, regardless of the [process creation flags]
|
||||
passed to [`CreateProcess`] and its subsystem value.
|
||||
|
||||
It would look (roughly) like this:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application>
|
||||
<windowsSettings>
|
||||
<consoleAllocationPolicy xmlns="http://schemas.microsoft.com/SMI/2024/WindowsSettings">detached</consoleAllocationPolicy>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
```
|
||||
|
||||
The effects of this field will only apply to binaries in the `IMAGE_SUBSYSTEM_WINDOWS_CUI` subsystem, as it pertains to
|
||||
the particulars of their console allocation.
|
||||
|
||||
**All console inheritance will proceed as normal.** Since this field takes effect only in the absence of console
|
||||
inheritance, CUI applications will still be able to run inside an existing console session.
|
||||
|
||||
| policy | behavior |
|
||||
| - | - |
|
||||
| _absent_ | _default behavior_ |
|
||||
| `detached` | The new process is not attached to a console session (similar to `DETACHED_PROCESS`) unless one was inherited. |
|
||||
|
||||
An application that specifies the `detached` allocation policy will _not_ present a console window when launched by
|
||||
Explorer, Task Scheduler, etc.
|
||||
|
||||
### Interaction with existing APIs
|
||||
|
||||
[`CreateProcess`] supports a number of [process creation flags] that dictate how a spawned application will behave with
|
||||
regards to console allocation:
|
||||
|
||||
* `DETACHED_PROCESS`: No console inheritance, no console host spawned for the new process.
|
||||
* `CREATE_NEW_CONSOLE`: No console inheritance, new console host **is** spawned for the new process.
|
||||
* `CREATE_NO_WINDOW`: No console inheritance, new console host **is** spawned for the new process.
|
||||
* this is the same as `CREATE_NEW_CONSOLE`, except that the first connection packet specifies that the window should
|
||||
be invisible
|
||||
|
||||
Due to the design of [`CreateProcess`] and `ShellExecute`, this specification recommends that an allocation policy of
|
||||
`detached` _override_ the inclusion of `CREATE_NEW_CONSOLE` in the `dwFlags` parameter to [`CreateProcess`].
|
||||
|
||||
> **Note**
|
||||
> `ShellExecute` passes `CREATE_NEW_CONSOLE` _by default_ on all invocations. This impacts our ability to resolve the
|
||||
> conflicts between these two APIs--`detached` policy and `CREATE_NEW_CONSOLE`--without auditing every call site in
|
||||
> every Windows application that calls `ShellExecute` on a console application. Doing so is infeasible.
|
||||
|
||||
### Application impact
|
||||
|
||||
An application that opts into the `detached` console allocation policy will **not** be allocated a console unless one is
|
||||
inherited. This presents an issue for applications like PowerShell that do want a console window when they are launched
|
||||
directly.
|
||||
|
||||
Applications in this category can call `AllocConsole()` early in their startup to get fine-grained control over when a
|
||||
console is presented.
|
||||
|
||||
The call to `AllocConsole()` will fail safely if the application has already inherited a console handle. It will succeed
|
||||
if the application does not currently have a console handle.
|
||||
|
||||
> **Note**
|
||||
> **Backwards Compatibility**: The behavior of `AllocConsole()` is not changing in response to this specification;
|
||||
> therefore, applications that intend to run on older versions of Windows that do not support console allocation
|
||||
> policies, which call `AllocConsole()`, will continue to behave normally.
|
||||
|
||||
### New APIs
|
||||
|
||||
Because a console-subsystem application may still want fine-grained control over when and how its console window is
|
||||
spawned, we propose the inclusion of a new API, `AllocConsoleWithOptions(PALLOC_CONSOLE_OPTIONS)`.
|
||||
|
||||
#### `AllocConsoleWithOptions`
|
||||
|
||||
```c++
|
||||
// Console Allocation Modes
|
||||
typedef enum ALLOC_CONSOLE_MODE {
|
||||
ALLOC_CONSOLE_MODE_DEFAULT = 0,
|
||||
ALLOC_CONSOLE_MODE_NEW_WINDOW = 1,
|
||||
ALLOC_CONSOLE_MODE_NO_WINDOW = 2
|
||||
} ALLOC_CONSOLE_MODE;
|
||||
|
||||
typedef enum ALLOC_CONSOLE_RESULT {
|
||||
ALLOC_CONSOLE_RESULT_NO_CONSOLE = 0,
|
||||
ALLOC_CONSOLE_RESULT_NEW_CONSOLE = 1,
|
||||
ALLOC_CONSOLE_RESULT_EXISTING_CONSOLE = 2
|
||||
} ALLOC_CONSOLE_RESULT, *PALLOC_CONSOLE_RESULT;
|
||||
|
||||
typedef
|
||||
struct ALLOC_CONSOLE_OPTIONS
|
||||
{
|
||||
ALLOC_CONSOLE_MODE mode;
|
||||
BOOL useShowWindow;
|
||||
WORD showWindow;
|
||||
} ALLOC_CONSOLE_OPTIONS, *PALLOC_CONSOLE_OPTIONS;
|
||||
|
||||
WINBASEAPI
|
||||
HRESULT
|
||||
WINAPI
|
||||
AllocConsoleWithOptions(_In_opt_ PALLOC_CONSOLE_OPTIONS allocOptions, _Out_opt_ PALLOC_CONSOLE_RESULT result);
|
||||
```
|
||||
|
||||
**AllocConsoleWithOptions** affords an application control over how and when it begins a console session.
|
||||
|
||||
> [!NOTE]
|
||||
> Unlike `AllocConsole`, `AllocConsoleWithOptions` without a mode (`ALLOC_CONSOLE_MODE_DEFAULT`) will only allocate a console if one was
|
||||
> requested during `CreateProcess`.
|
||||
>
|
||||
> To override this behavior, pass one of `ALLOC_CONSOLE_MODE_NEW_WINDOW` (which is equivalent to being spawned with
|
||||
> `CREATE_NEW_WINDOW`) or `ALLOC_CONSOLE_MODE_NO_WINDOW` (which is equivalent to being spawned with `CREATE_NO_CONSOLE`.)
|
||||
|
||||
##### Parameters
|
||||
|
||||
**allocOptions**: A pointer to a `ALLOC_CONSOLE_OPTIONS`.
|
||||
|
||||
**result**: An optional out pointer, which will be populated with a member of the `ALLOC_CONSOLE_RESULT` enum.
|
||||
|
||||
##### `ALLOC_CONSOLE_OPTIONS`
|
||||
|
||||
###### Members
|
||||
|
||||
**mode**: See the table below for the descriptions of the available modes.
|
||||
|
||||
**useShowWindow**: Specifies whether the value in `showWindow` should be used.
|
||||
|
||||
**showWindow**: If `useShowWindow` is set, specifies the ["show command"] used to display your
|
||||
console window.
|
||||
|
||||
###### Return Value
|
||||
|
||||
`AllocConsoleWithOptions` will return `S_OK` and populate `result` to indicate whether--and how--a console session was
|
||||
created.
|
||||
|
||||
`AllocConsoleWithOptions` will return a failing `HRESULT` if the request could not be completed.
|
||||
|
||||
###### Modes
|
||||
|
||||
| Mode | Description |
|
||||
|:-------------------------------:| ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `ALLOC_CONSOLE_MODE_DEFAULT` | Allocate a console session if (and how) one was requested by the parent process. |
|
||||
| `ALLOC_CONSOLE_MODE_NEW_WINDOW` | Allocate a console session with a window, even if this process was created with `CREATE_NO_CONSOLE` or `DETACHED_PROCESS`. |
|
||||
| `ALLOC_CONSOLE_MODE_NO_WINDOW` | Allocate a console session _without_ a window, even if this process was created with `CREATE_NEW_WINDOW` or `DETACHED_PROCESS` |
|
||||
|
||||
###### Notes
|
||||
|
||||
Applications seeking backwards compatibility are encouraged to delay-load `AllocConsoleWithOptions` or check for its presence in
|
||||
the `api-ms-win-core-console-l1` APISet.
|
||||
|
||||
## Inspiration
|
||||
|
||||
Fusion manifest entries are used to make application-scoped decisions like this all the time, like `longPathAware` and
|
||||
`heapType`.
|
||||
|
||||
CUI applications that can spawn a UI (or GUI applications that can print to a console) are commonplace on other
|
||||
platforms because there is no subsystem differentiation.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
There is no UI for this feature.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
This should have no impact on accessibility.
|
||||
|
||||
### Security
|
||||
|
||||
One reviewer brought up the potential for a malicious actor to spawn an endless stream of headless daemon processes.
|
||||
|
||||
This proposal in no way changes the facilities available to malicious people for causing harm: they could have simply
|
||||
used `IMAGE_SUBSYSTEM_WINDOWS_GUI` and not presented a UI--an option that has been available to them for 35 years.
|
||||
|
||||
### Reliability
|
||||
|
||||
This should have no impact on reliability.
|
||||
|
||||
### Compatibility
|
||||
|
||||
An existing application opting into **detached** may constitute a breaking change, but the scope of the breakage is
|
||||
restricted to that application and is expected to be managed by the application.
|
||||
|
||||
All behavioral changes are opt-in.
|
||||
|
||||
> **EXAMPLE**: If Python updates python.exe to specify an allocation policy of **detached**, graphical python applications
|
||||
> will become double-click runnable from the graphical shell without spawning a console window. _However_, console-based
|
||||
> python applications will no longer spawn a console window when double-clicked from the graphical shell.
|
||||
>
|
||||
> In addition, if python.exe specifies **detached**, Console APIs will fail until a console is allocated.
|
||||
|
||||
Python could work around this by calling [`AllocConsole`] or [new API `AllocConsoleWithOptions`](#allocconsolewithoptions)
|
||||
if it can be detected that console I/O is required.
|
||||
|
||||
#### Downlevel
|
||||
|
||||
On downlevel versions of Windows that do not understand (or expect) this manifest field, applications will allocate
|
||||
consoles as specified by their image subsystem (described in the [abstract](#abstract) above).
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
This should have no impact on performance, power or efficiency.
|
||||
|
||||
## Potential Issues
|
||||
|
||||
### Shell Hang
|
||||
|
||||
I am **not** proposing a change in how shells determine whether to wait for an application before returning to a prompt.
|
||||
This means that a console subsystem application that intends to primarily present a UI but occasionally print text to a
|
||||
console (therefore choosing the **detached** allocation policy) will cause the shell to "hang" and wait for it to
|
||||
exit.
|
||||
|
||||
The decision to pause/wait is made entirely in the calling shell, and the console subsystem cannot influence that
|
||||
decision.
|
||||
|
||||
Because the vast majority of shells on Windows "hang" by calling `WaitFor...Object` with a HANDLE to the spawned
|
||||
process, an application that wants to be a "hybrid" CUI/GUI application will be forced to spawn a separate process to
|
||||
detach from the shell and then terminate its main process.
|
||||
|
||||
This is very similar to the forking model seen in many POSIX-compliant operating systems.
|
||||
|
||||
### Launching interactively from Explorer, Task Scheduler, etc.
|
||||
|
||||
Applications like PowerShell may wish to retain automatic console allocation, and **detached** would be unsuitable for
|
||||
them. If PowerShell specifies the `detached` console allocation policy, launching `pwsh.exe` from File Explorer it will
|
||||
no longer spawn a console. This would almost certainly break PowerShell for all users.
|
||||
|
||||
Such applications can use `AllocConsole()` early in their startup.
|
||||
|
||||
At the same time, PowerShell wants `-WindowStyle Hidden` to suppress the console _before it's created_.
|
||||
|
||||
Applications in this category can use `AllocConsoleWithOptions()` to specify additional information about the new console window.
|
||||
|
||||
PowerShell, and any other shell that wishes to maintain interactive launch from the graphical shell, can start in
|
||||
**detached** mode and then allocate a console as necessary. Therefore:
|
||||
|
||||
* PowerShell will set `<consoleAllocationPolicy>detached</consoleAllocationPolicy>`
|
||||
* On startup, it will process its commandline arguments.
|
||||
* If `-WindowStyle Hidden` is **not** present (the default case), it can:
|
||||
* `AllocConsole()` or `AllocConsoleWithOptions(NULL)`
|
||||
* Either of these APIs will present a console window (or not) based on the flags passed through `STARTUPINFO` during
|
||||
[`CreateProcess`].
|
||||
* If `-WindowStyle Hidden` is present, it can:
|
||||
* `AllocConsoleWithOptions(&alloc)` where `alloc.mode` specifies `ALLOC_CONSOLE_MODE_HIDDEN`
|
||||
|
||||
## Future considerations
|
||||
|
||||
We're introducing a new manifest field today -- what if we want to introduce more? Should we have a `consoleSettings`
|
||||
manifest block?
|
||||
|
||||
Are there other allocation policies we need to consider?
|
||||
|
||||
## Resources
|
||||
|
||||
### Rejected Solutions
|
||||
|
||||
- A new PE subsystem, `IMAGE_SUBSYSTEM_WINDOWS_HYBRID`
|
||||
- it would behave like **inheritOnly**
|
||||
- relies on shells to update and check for this
|
||||
- checking a subsystem doesn't work right with app execution aliases[^3]
|
||||
- This is not a new problem, but it digs the hole a little deeper.
|
||||
- requires standardization outside of Microsoft because the PE format is a dependency of the UEFI specification[^4]
|
||||
- requires coordination between tooling teams both within and without Microsoft (regarding any tool that operates on
|
||||
or produces PE files)
|
||||
|
||||
- An exported symbol that shells can check for to determine whether to wait for the attached process to exit
|
||||
- relies on shells to update and check for this
|
||||
- cracking an executable to look for symbols is probably the last thing shells want to do
|
||||
- we could provide an API to determine whether to wait or return?
|
||||
- fragile, somewhat silly, exporting symbols from EXEs is annoying and uncommon
|
||||
|
||||
An earlier version of this specification offered the **always** allocation policy, with the following behaviors:
|
||||
|
||||
> **STRUCK FROM SPECIFICATION**
|
||||
>
|
||||
> * A GUI subsystem application would always get a console window.
|
||||
> * A command-line shell would not wait for it to exit before returning a prompt.
|
||||
|
||||
It was cut because a GUI application that wants a console window can simply attach to an existing console session or
|
||||
allocate a new one. We found no compelling use case that would require the forced allocation of a console session
|
||||
outside of the application's code.
|
||||
|
||||
An earlier version of this specification offered the **inheritOnly** allocation policy, instead of the finer-grained
|
||||
**hidden** and **detached** policies. We deemed it insufficient for PowerShell's use case because any application
|
||||
launched by an **inheritOnly** PowerShell would immediately force the uncontrolled allocation of a console window.
|
||||
|
||||
> **STRUCK FROM SPECIFICATION**
|
||||
>
|
||||
> The move to **hidden** allows PowerShell to offer a fully-fledged console connection that can be itself inherited by a
|
||||
> downstream application.
|
||||
|
||||
#### Additional allocation policies
|
||||
|
||||
An earlier revision of this specification suggested two allocation policies:
|
||||
|
||||
> **STRUCK FROM SPECIFICATION**
|
||||
>
|
||||
> **hidden** is intended to be used by console applications that want finer-grained control over the visibility of their
|
||||
> console windows, but that still need a console host to service console APIs. This includes most scripting language
|
||||
> interpreters.
|
||||
>
|
||||
> **detached** is intended to be used by primarily graphical applications that would like to operate against a console _if
|
||||
> one is present_ but do not mind its absence. This includes any graphical tool with a `--help` or `/?` argument.
|
||||
|
||||
The `hidden` policy was rejected due to an incompatibility with modern console hosting, as `hidden` would require an
|
||||
application to interact with the console window via `GetConsoleWindow()` and explicitly show it.
|
||||
|
||||
> **STRUCK FROM SPECIFICATION**
|
||||
>
|
||||
> ##### ShowWindow and ConPTY
|
||||
>
|
||||
> The pseudoconsole creates a hidden window to service `GetConsoleWindow()`, and it can be trivially shown using
|
||||
> `ShowWindow`. If we recommend that applications `ShowWindow` on startup, we will need to guard the pseudoconsole's
|
||||
> pseudo-window from being shown.
|
||||
|
||||
[^1]: [Powershell -WindowStyle Hidden still shows a window briefly]
|
||||
[^2]: [StackOverflow: pythonw.exe or python.exe?]
|
||||
[^3]: [PowerShell: Windows Store applications incorrectly assumed to be console applications]
|
||||
[^4]: [UEFI spec 2.6 appendix Q.1]
|
||||
|
||||
[Powershell -WindowStyle Hidden still shows a window briefly]: https://github.com/PowerShell/PowerShell/issues/3028
|
||||
[PowerShell: Windows Store applications incorrectly assumed to be console applications]: https://github.com/PowerShell/PowerShell/issues/9970
|
||||
[StackOverflow: pythonw.exe or python.exe?]: https://stackoverflow.com/questions/9705982/pythonw-exe-or-python-exe
|
||||
[UEFI spec 2.6 appendix Q.1]: https://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf
|
||||
[`AllocConsole`]: https://docs.microsoft.com/windows/console/allocconsole
|
||||
[`CreateProcess`]: https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
|
||||
[process creation flags]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
|
||||
["show command"]: https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-showwindow
|
||||
407
l9.cpp
Normal file
407
l9.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Module Name:
|
||||
callserver.c
|
||||
Abstract:
|
||||
This module contains the implementation of the routines used by console
|
||||
clients in all layers to call console servers.
|
||||
Author:
|
||||
Wedson Almeida Filho (wedsonaf) 14-Jun-2010
|
||||
Environment:
|
||||
User mode.
|
||||
--*/
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4201) // nameless struct/union
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
/////
|
||||
#define CONSOLE_FIRST_API_NUMBER(Layer) \
|
||||
(Layer << 24)
|
||||
|
||||
typedef struct _CD_IO_BUFFER
|
||||
{
|
||||
ULONG Size;
|
||||
PVOID Buffer;
|
||||
} CD_IO_BUFFER, *PCD_IO_BUFFER;
|
||||
|
||||
typedef struct _CD_IO_BUFFER64
|
||||
{
|
||||
ULONG Size;
|
||||
PVOID64 Buffer;
|
||||
} CD_IO_BUFFER64, *PCD_IO_BUFFER64;
|
||||
|
||||
typedef struct _CD_USER_DEFINED_IO
|
||||
{
|
||||
HANDLE Client;
|
||||
ULONG InputCount;
|
||||
ULONG OutputCount;
|
||||
CD_IO_BUFFER Buffers[ANYSIZE_ARRAY];
|
||||
} CD_USER_DEFINED_IO, *PCD_USER_DEFINED_IO;
|
||||
|
||||
typedef struct _CD_USER_DEFINED_IO64
|
||||
{
|
||||
PVOID64 Client;
|
||||
ULONG InputCount;
|
||||
ULONG OutputCount;
|
||||
CD_IO_BUFFER64 Buffers[ANYSIZE_ARRAY];
|
||||
} CD_USER_DEFINED_IO64, *PCD_USER_DEFINED_IO64;
|
||||
|
||||
typedef struct _CONSOLE_MSG_HEADER
|
||||
{
|
||||
ULONG ApiNumber;
|
||||
ULONG ApiDescriptorSize;
|
||||
} CONSOLE_MSG_HEADER, *PCONSOLE_MSG_HEADER;
|
||||
|
||||
typedef struct _CONSOLE_BUFFER
|
||||
{
|
||||
ULONG Size;
|
||||
PVOID Buffer;
|
||||
} CONSOLE_BUFFER, *PCONSOLE_BUFFER;
|
||||
|
||||
#define IOCTL_CONDRV_ISSUE_USER_IO \
|
||||
CTL_CODE(FILE_DEVICE_CONSOLE, 5, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
|
||||
#pragma region L9
|
||||
|
||||
typedef enum _CONSOLE_API_NUMBER_L9 {
|
||||
ConsoleTestApi = CONSOLE_FIRST_API_NUMBER(9),
|
||||
} CONSOLE_API_NUMBER_L9, *PCONSOLE_API_NUMBER_L9;
|
||||
|
||||
typedef struct _CONSOLE_L9_TEST_API {
|
||||
IN ULONG TestValue;
|
||||
IN PVOID64 APCFunc;
|
||||
OUT ULONG ReplyValue;
|
||||
} CONSOLE_L9_TEST_API, *PCONSOLE_L9_TEST_API;
|
||||
|
||||
typedef union _CONSOLE_MSG_BODY_L9 {
|
||||
CONSOLE_L9_TEST_API TestApi;
|
||||
} CONSOLE_MSG_BODY_L9, *PCONSOLE_MSG_BODY_L9;
|
||||
|
||||
#ifndef __cplusplus
|
||||
typedef struct _CONSOLE_MSG_L9 {
|
||||
CONSOLE_MSG_HEADER Header;
|
||||
union {
|
||||
CONSOLE_MSG_BODY_L9;
|
||||
} u;
|
||||
} CONSOLE_MSG_L9, *PCONSOLE_MSG_L9;
|
||||
#else
|
||||
typedef struct _CONSOLE_MSG_L9 :
|
||||
public CONSOLE_MSG_HEADER
|
||||
{
|
||||
CONSOLE_MSG_BODY_L9 u;
|
||||
} CONSOLE_MSG_L9, *PCONSOLE_MSG_L9;
|
||||
#endif // __cplusplus
|
||||
|
||||
#pragma endregion
|
||||
|
||||
HANDLE ConsoleGetHandle() noexcept
|
||||
{
|
||||
return GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
|
||||
HRESULT
|
||||
ConsoleCallServerGeneric(
|
||||
__in HANDLE Handle,
|
||||
__in_opt HANDLE ClientHandle,
|
||||
__inout_bcount(sizeof(CONSOLE_MSG_HEADER) + ArgumentSize) PCONSOLE_MSG_HEADER Header,
|
||||
__in ULONG ApiNumber,
|
||||
__in ULONG ArgumentSize,
|
||||
__in_ecount_opt(InputCount) PCONSOLE_BUFFER InputBuffers,
|
||||
__in ULONG InputCount,
|
||||
__in_ecount_opt(OutputCount) PCONSOLE_BUFFER OutputBuffers,
|
||||
__in ULONG OutputCount)
|
||||
|
||||
/*++
|
||||
|
||||
Routine Description:
|
||||
|
||||
This routine sends a request to the console server associated with the
|
||||
given console object.
|
||||
|
||||
Arguments:
|
||||
|
||||
Handle - Supplies either a client handle (in which case ClientHandle
|
||||
should be NULL) or a connection handle (in which case ClientHandle
|
||||
may be non-NULL).
|
||||
|
||||
ClientHandle - Supplies the console object whose server will receive the
|
||||
request, when Handle is a handle to a connection object.
|
||||
|
||||
Header - Supplies the header of the contents of the request to be sent to
|
||||
the server. The actual payload must follow the header.
|
||||
|
||||
ApiNumber - Supplies the number of request the be sent to the server.
|
||||
|
||||
ArgumentSize - Supplies the size, in bytes, of the request payload that
|
||||
follows the header.
|
||||
|
||||
InputBuffers - Optionally supplies an array of additional input buffers.
|
||||
|
||||
InputCount - Supplies the number of elements in the input buffer array.
|
||||
|
||||
OutputBuffers - Optionally supplies an array of additional output buffers.
|
||||
|
||||
OutputCount - Supplies the number of elements in the output buffer array.
|
||||
|
||||
Return Value:
|
||||
|
||||
HRESULT indicating the result of the call. The server determine the
|
||||
result.
|
||||
|
||||
--*/
|
||||
|
||||
{
|
||||
ULONG Count;
|
||||
struct
|
||||
{
|
||||
#if defined(BUILD_WOW6432) && !defined(BUILD_WOW3232)
|
||||
|
||||
CD_USER_DEFINED_IO64 Io;
|
||||
CD_IO_BUFFER64 Buffers[10];
|
||||
|
||||
#else
|
||||
|
||||
CD_USER_DEFINED_IO Io;
|
||||
CD_IO_BUFFER Buffers[10];
|
||||
|
||||
#endif
|
||||
|
||||
} Descriptors;
|
||||
ULONG Index;
|
||||
ULONG InSize;
|
||||
HRESULT Status;
|
||||
|
||||
Count = InputCount + OutputCount + 2;
|
||||
if (Count > 10)
|
||||
{
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
if ((LONG_PTR)Handle <= 0)
|
||||
{
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize the input and ouput descriptors containing the message.
|
||||
//
|
||||
|
||||
Header->ApiNumber = ApiNumber;
|
||||
Header->ApiDescriptorSize = ArgumentSize;
|
||||
|
||||
Descriptors.Io.Buffers[0].Buffer = Header;
|
||||
Descriptors.Io.Buffers[0].Size = sizeof(CONSOLE_MSG_HEADER) + ArgumentSize;
|
||||
|
||||
Descriptors.Io.Buffers[InputCount + 1].Buffer = Header + 1;
|
||||
Descriptors.Io.Buffers[InputCount + 1].Size = ArgumentSize;
|
||||
|
||||
//
|
||||
// Initialize the potential additional descriptors and calculate the input
|
||||
// size.
|
||||
//
|
||||
|
||||
Descriptors.Io.Client = ClientHandle;
|
||||
Descriptors.Io.InputCount = InputCount + 1;
|
||||
Descriptors.Io.OutputCount = OutputCount + 1;
|
||||
|
||||
for (Index = 0; Index < InputCount; Index += 1)
|
||||
{
|
||||
Descriptors.Io.Buffers[Index + 1].Size = InputBuffers[Index].Size;
|
||||
Descriptors.Io.Buffers[Index + 1].Buffer = InputBuffers[Index].Buffer;
|
||||
}
|
||||
|
||||
for (Index = 0; Index < OutputCount; Index += 1)
|
||||
{
|
||||
Descriptors.Io.Buffers[Index + InputCount + 2].Size =
|
||||
OutputBuffers[Index].Size;
|
||||
|
||||
Descriptors.Io.Buffers[Index + InputCount + 2].Buffer =
|
||||
OutputBuffers[Index].Buffer;
|
||||
}
|
||||
|
||||
InSize =
|
||||
|
||||
#if defined(BUILD_WOW6432) && !defined(BUILD_WOW3232)
|
||||
|
||||
Count * sizeof(CD_IO_BUFFER64) + FIELD_OFFSET(CD_USER_DEFINED_IO64, Buffers);
|
||||
|
||||
#else
|
||||
|
||||
Count * sizeof(CD_IO_BUFFER) + FIELD_OFFSET(CD_USER_DEFINED_IO, Buffers);
|
||||
|
||||
#endif
|
||||
|
||||
//
|
||||
// Issue the io.
|
||||
//
|
||||
|
||||
DWORD dwOut{};
|
||||
BOOL success = DeviceIoControl(
|
||||
Handle,
|
||||
IOCTL_CONDRV_ISSUE_USER_IO,
|
||||
&Descriptors,
|
||||
InSize,
|
||||
nullptr,
|
||||
0,
|
||||
&dwOut,
|
||||
nullptr);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
ConsoleCallServerWithBuffers(
|
||||
__in HANDLE Handle,
|
||||
__inout_bcount(sizeof(CONSOLE_MSG_HEADER) + ArgumentSize) PCONSOLE_MSG_HEADER Header,
|
||||
__in ULONG ApiNumber,
|
||||
__in ULONG ArgumentSize,
|
||||
__in_ecount_opt(InputCount) PCONSOLE_BUFFER InputBuffers,
|
||||
__in ULONG InputCount,
|
||||
__in_ecount_opt(OutputCount) PCONSOLE_BUFFER OutputBuffers,
|
||||
__in ULONG OutputCount)
|
||||
|
||||
/*++
|
||||
|
||||
Routine Description:
|
||||
|
||||
This routine sends a request to the console server associated with the
|
||||
given console object. The connection handle will be used to carry the
|
||||
request.
|
||||
|
||||
Arguments:
|
||||
|
||||
Handle - Supplies either a client handle (in which case ClientHandle
|
||||
should be NULL) or a connection handle (in which case ClientHandle
|
||||
may be non-NULL).
|
||||
|
||||
ClientHandle - Supplies the console object whose server will receive the
|
||||
request, when Handle is a handle to a connection object.
|
||||
|
||||
Header - Supplies the header of the contents of the request to be sent to
|
||||
the server. The actual payload must follow the header.
|
||||
|
||||
ApiNumber - Supplies the number of request the be sent to the server.
|
||||
|
||||
ArgumentSize - Supplies the size, in bytes, of the request payload that
|
||||
follows the header.
|
||||
|
||||
InputBuffers - Optionally supplies an array of additional input buffers.
|
||||
|
||||
InputCount - Supplies the number of elements in the input buffer array.
|
||||
|
||||
OutputBuffers - Optionally supplies an array of additional output buffers.
|
||||
|
||||
OutputCount - Supplies the number of elements in the output buffer array.
|
||||
|
||||
Return Value:
|
||||
|
||||
HRESULT indicating the result of the call. The server determine the
|
||||
result.
|
||||
|
||||
--*/
|
||||
|
||||
{
|
||||
return ConsoleCallServerGeneric(ConsoleGetHandle(),
|
||||
Handle,
|
||||
Header,
|
||||
ApiNumber,
|
||||
ArgumentSize,
|
||||
InputBuffers,
|
||||
InputCount,
|
||||
OutputBuffers,
|
||||
OutputCount);
|
||||
}
|
||||
|
||||
HRESULT
|
||||
ConsoleCallServer(
|
||||
__in HANDLE Handle,
|
||||
__inout_bcount(sizeof(CONSOLE_MSG_HEADER) + ArgumentSize) PCONSOLE_MSG_HEADER Header,
|
||||
__in ULONG ApiNumber,
|
||||
__in ULONG ArgumentSize)
|
||||
|
||||
/*++
|
||||
|
||||
Routine Description:
|
||||
|
||||
This routine sends a request to the console server associated with the
|
||||
given console object. The connection handle will be used to carry the
|
||||
request.
|
||||
|
||||
Arguments:
|
||||
|
||||
Handle - Supplies the console object whose server will receive the
|
||||
request.
|
||||
|
||||
Header - Supplies the header of the contents of the request to be sent to
|
||||
the server. The actual payload must follow the header.
|
||||
|
||||
ApiNumber - Supplies the number of request the be sent to the server.
|
||||
|
||||
ArgumentSize - Supplies the size, in bytes, of the request payload that
|
||||
follows the header.
|
||||
|
||||
Return Value:
|
||||
|
||||
NTSTATUS indicating the result of the call. The server determine the
|
||||
result.
|
||||
|
||||
--*/
|
||||
|
||||
{
|
||||
return ConsoleCallServerGeneric(ConsoleGetHandle(),
|
||||
Handle,
|
||||
Header,
|
||||
ApiNumber,
|
||||
ArgumentSize,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
0);
|
||||
}
|
||||
|
||||
void MyAPCHandler(ULONG_PTR data);
|
||||
ULONG TestL9()
|
||||
{
|
||||
CONSOLE_MSG_L9 m;
|
||||
PCONSOLE_L9_TEST_API a = &m.u.TestApi;
|
||||
a->TestValue = 1024;
|
||||
a->APCFunc = (PVOID64)&MyAPCHandler;
|
||||
HRESULT hr = ConsoleCallServer(ConsoleGetHandle(), &m, ConsoleTestApi, sizeof(*a));
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
return a->ReplyValue;
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
void MyAPCHandler(ULONG_PTR data) {
|
||||
fprintf(stderr, "Received APC: %8.08llx\n", data);
|
||||
}
|
||||
|
||||
BOOL WINAPI CtrlHandler(DWORD ctrlType)
|
||||
{
|
||||
if (ctrlType == 0xA0) {
|
||||
fprintf(stderr, "Received a good signal 0xA0!\n");
|
||||
return TRUE;
|
||||
}
|
||||
fprintf(stderr, "Received a signal %x\n", ctrlType);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int main() {
|
||||
SetConsoleCtrlHandler(&CtrlHandler, TRUE);
|
||||
auto rep = TestL9();
|
||||
fprintf(stderr, "Reply Val %8.08llx\n", (unsigned long long)rep);
|
||||
SleepEx(2000, TRUE);
|
||||
return 0;
|
||||
}
|
||||
@@ -92,47 +92,35 @@ CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* cha
|
||||
|
||||
// If given a position (`offset`) inside the ROW's text, this function will return the corresponding column.
|
||||
// This function in particular returns the glyph's first column.
|
||||
til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t offset) noexcept
|
||||
til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t targetOffset) noexcept
|
||||
{
|
||||
offset = clamp(offset, 0, _lastCharOffset);
|
||||
targetOffset = clamp(targetOffset, 0, _lastCharOffset);
|
||||
|
||||
// This code needs to fulfill two conditions on top of the obvious (a forward/backward search):
|
||||
// A: We never want to stop on a column that is marked with CharOffsetsTrailer (= "GetLeadingColumn").
|
||||
// B: With these parameters we always want to stop at currentOffset=4:
|
||||
// _charOffsets={4, 6}
|
||||
// currentOffset=4 *OR* 6
|
||||
// targetOffset=5
|
||||
// This is because we're being asked for a "LeadingColumn", while the caller gave us the offset of a
|
||||
// trailing surrogate pair or similar. Returning the column of the leading half is the correct choice.
|
||||
|
||||
auto col = _currentColumn;
|
||||
const auto currentOffset = _charOffsets[col];
|
||||
auto currentOffset = _charOffsets[col];
|
||||
|
||||
// Goal: Move the _currentColumn cursor to a cell which contains the given target offset.
|
||||
// Depending on where the target offset is we have to either search forward or backward.
|
||||
if (offset < currentOffset)
|
||||
// A plain forward-search until we find our targetOffset.
|
||||
// This loop may iterate too far and thus violate our example in condition B, however...
|
||||
while (targetOffset > (currentOffset & CharOffsetsMask))
|
||||
{
|
||||
// Backward search.
|
||||
// Goal: Find the first preceding column where the offset is <= the target offset. This results in the first
|
||||
// cell that contains our target offset, even if that offset is in the middle of a long grapheme.
|
||||
//
|
||||
// We abuse the fact that the trailing half of wide glyphs is marked with CharOffsetsTrailer to our advantage.
|
||||
// Since they're >0x8000, the `offset < _charOffsets[col]` check will always be true and ensure we iterate over them.
|
||||
//
|
||||
// Since _charOffsets cannot contain negative values and because offset has been
|
||||
// clamped to be positive we naturally exit when reaching the first column.
|
||||
for (; offset < _charOffsets[col - 1]; --col)
|
||||
{
|
||||
}
|
||||
currentOffset = _charOffsets[++col];
|
||||
}
|
||||
else if (offset > currentOffset)
|
||||
// This backward-search is not just a counter-part to the above, but simultaneously also handles conditions A and B.
|
||||
// It abuses the fact that columns marked with CharOffsetsTrailer are >0x8000 and targetOffset is always <0x8000.
|
||||
// This means we skip all "trailer" columns when iterating backwards, and only stop on a non-trailer (= condition A).
|
||||
// Condition B is fixed simply because we iterate backwards after the forward-search (in that exact order).
|
||||
while (targetOffset < currentOffset)
|
||||
{
|
||||
// Forward search.
|
||||
// Goal: Find the first subsequent column where the offset is > the target offset.
|
||||
// We stop 1 column before that however so that the next loop works correctly.
|
||||
// It's the inverse of the loop above.
|
||||
//
|
||||
// Since offset has been clamped to be at most 1 less than the maximum
|
||||
// _charOffsets value the loop naturally exits before hitting the end.
|
||||
for (; offset >= (_charOffsets[col + 1] & CharOffsetsMask); ++col)
|
||||
{
|
||||
}
|
||||
// Now that we found the cell that definitely includes this char offset,
|
||||
// we have to iterate back to the cell's starting column.
|
||||
for (; WI_IsFlagSet(_charOffsets[col], CharOffsetsTrailer); --col)
|
||||
{
|
||||
}
|
||||
currentOffset = _charOffsets[--col];
|
||||
}
|
||||
|
||||
_currentColumn = col;
|
||||
@@ -404,6 +392,18 @@ til::CoordType ROW::AdjustToGlyphStart(til::CoordType column) const noexcept
|
||||
return _adjustBackward(_clampedColumn(column));
|
||||
}
|
||||
|
||||
// Returns the (exclusive) ending column of the glyph at the given column.
|
||||
// In other words, if you have 3 wide glyphs
|
||||
// AA BB CC
|
||||
// 01 23 45 <-- column
|
||||
// Examples:
|
||||
// - `AdjustToGlyphEnd(4)` returns 6.
|
||||
// - `AdjustToGlyphEnd(3)` returns 4.
|
||||
til::CoordType ROW::AdjustToGlyphEnd(til::CoordType column) const noexcept
|
||||
{
|
||||
return _adjustForward(_clampedColumnInclusive(column));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - clears char data in column in row
|
||||
// Arguments:
|
||||
@@ -939,36 +939,10 @@ uint16_t ROW::size() const noexcept
|
||||
return _columnCount;
|
||||
}
|
||||
|
||||
til::CoordType ROW::MeasureLeft() const noexcept
|
||||
// Routine Description:
|
||||
// - Retrieves the column that is one after the last non-space character in the row.
|
||||
til::CoordType ROW::GetLastNonSpaceColumn() const noexcept
|
||||
{
|
||||
const auto text = GetText();
|
||||
const auto beg = text.begin();
|
||||
const auto end = text.end();
|
||||
auto it = beg;
|
||||
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
if (*it != L' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return gsl::narrow_cast<til::CoordType>(it - beg);
|
||||
}
|
||||
|
||||
til::CoordType ROW::MeasureRight() const noexcept
|
||||
{
|
||||
if (_wrapForced)
|
||||
{
|
||||
auto width = _columnCount;
|
||||
if (_doubleBytePadded)
|
||||
{
|
||||
width--;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
const auto text = GetText();
|
||||
const auto beg = text.begin();
|
||||
const auto end = text.end();
|
||||
@@ -988,7 +962,42 @@ til::CoordType ROW::MeasureRight() const noexcept
|
||||
//
|
||||
// An example: The row is 10 cells wide and `it` points to the second character.
|
||||
// `it - beg` would return 1, but it's possible it's actually 1 wide glyph and 8 whitespace.
|
||||
return gsl::narrow_cast<til::CoordType>(_columnCount - (end - it));
|
||||
return gsl::narrow_cast<til::CoordType>(GetReadableColumnCount() - (end - it));
|
||||
}
|
||||
|
||||
til::CoordType ROW::MeasureLeft() const noexcept
|
||||
{
|
||||
const auto text = GetText();
|
||||
const auto beg = text.begin();
|
||||
const auto end = text.end();
|
||||
auto it = beg;
|
||||
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
if (*it != L' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return gsl::narrow_cast<til::CoordType>(it - beg);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the column that is one after the last valid character in the row.
|
||||
til::CoordType ROW::MeasureRight() const noexcept
|
||||
{
|
||||
if (_wrapForced)
|
||||
{
|
||||
auto width = _columnCount;
|
||||
if (_doubleBytePadded)
|
||||
{
|
||||
width--;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
return GetLastNonSpaceColumn();
|
||||
}
|
||||
|
||||
bool ROW::ContainsText() const noexcept
|
||||
|
||||
@@ -71,7 +71,7 @@ struct CharToColumnMapper
|
||||
{
|
||||
CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t lastCharOffset, til::CoordType currentColumn) noexcept;
|
||||
|
||||
til::CoordType GetLeadingColumnAt(ptrdiff_t offset) noexcept;
|
||||
til::CoordType GetLeadingColumnAt(ptrdiff_t targetOffset) noexcept;
|
||||
til::CoordType GetTrailingColumnAt(ptrdiff_t offset) noexcept;
|
||||
til::CoordType GetLeadingColumnAt(const wchar_t* str) noexcept;
|
||||
til::CoordType GetTrailingColumnAt(const wchar_t* str) noexcept;
|
||||
@@ -137,6 +137,7 @@ public:
|
||||
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
|
||||
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
|
||||
til::CoordType AdjustToGlyphStart(til::CoordType column) const noexcept;
|
||||
til::CoordType AdjustToGlyphEnd(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);
|
||||
@@ -151,6 +152,7 @@ public:
|
||||
TextAttribute GetAttrByColumn(til::CoordType column) const;
|
||||
std::vector<uint16_t> GetHyperlinks() const;
|
||||
uint16_t size() const noexcept;
|
||||
til::CoordType GetLastNonSpaceColumn() const noexcept;
|
||||
til::CoordType MeasureLeft() const noexcept;
|
||||
til::CoordType MeasureRight() const noexcept;
|
||||
bool ContainsText() const noexcept;
|
||||
|
||||
@@ -126,6 +126,8 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau
|
||||
// The compiler doesn't understand the likelihood of our branches. (PGO does, but that's imperfect.)
|
||||
__declspec(noinline) void TextBuffer::_commit(const std::byte* row)
|
||||
{
|
||||
assert(row >= _commitWatermark);
|
||||
|
||||
const auto rowEnd = row + _bufferRowStride;
|
||||
const auto remaining = gsl::narrow_cast<uintptr_t>(_bufferEnd - _commitWatermark);
|
||||
const auto minimum = gsl::narrow_cast<uintptr_t>(rowEnd - _commitWatermark);
|
||||
@@ -146,7 +148,7 @@ void TextBuffer::_decommit() noexcept
|
||||
_commitWatermark = _buffer.get();
|
||||
}
|
||||
|
||||
// Constructs ROWs up to (excluding) the ROW pointed to by `until`.
|
||||
// Constructs ROWs between [_commitWatermark,until).
|
||||
void TextBuffer::_construct(const std::byte* until) noexcept
|
||||
{
|
||||
for (; _commitWatermark < until; _commitWatermark += _bufferRowStride)
|
||||
@@ -158,8 +160,7 @@ void TextBuffer::_construct(const std::byte* until) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
// Destroys all previously constructed ROWs.
|
||||
// Be careful! This doesn't reset any of the members, in particular the _commitWatermark.
|
||||
// Destructs ROWs between [_buffer,_commitWatermark).
|
||||
void TextBuffer::_destroy() const noexcept
|
||||
{
|
||||
for (auto it = _buffer.get(); it < _commitWatermark; it += _bufferRowStride)
|
||||
@@ -168,9 +169,8 @@ void TextBuffer::_destroy() const noexcept
|
||||
}
|
||||
}
|
||||
|
||||
// This function is "direct" because it trusts the caller to properly wrap the "offset"
|
||||
// parameter modulo the _height of the buffer, etc. But keep in mind that a offset=0
|
||||
// is the GetScratchpadRow() and not the GetRowByOffset(0). That one is offset=1.
|
||||
// This function is "direct" because it trusts the caller to properly
|
||||
// wrap the "offset" parameter modulo the _height of the buffer.
|
||||
ROW& TextBuffer::_getRowByOffsetDirect(size_t offset)
|
||||
{
|
||||
const auto row = _buffer.get() + _bufferRowStride * offset;
|
||||
@@ -184,6 +184,7 @@ ROW& TextBuffer::_getRowByOffsetDirect(size_t offset)
|
||||
return *reinterpret_cast<ROW*>(row);
|
||||
}
|
||||
|
||||
// See GetRowByOffset().
|
||||
ROW& TextBuffer::_getRow(til::CoordType y) const
|
||||
{
|
||||
// Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows.
|
||||
@@ -197,6 +198,7 @@ ROW& TextBuffer::_getRow(til::CoordType y) const
|
||||
}
|
||||
|
||||
// We add 1 to the row offset, because row "0" is the one returned by GetScratchpadRow().
|
||||
// See GetScratchpadRow() for more explanation.
|
||||
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
|
||||
return const_cast<TextBuffer*>(this)->_getRowByOffsetDirect(gsl::narrow_cast<size_t>(offset) + 1);
|
||||
}
|
||||
@@ -238,6 +240,9 @@ ROW& TextBuffer::GetScratchpadRow()
|
||||
// Returns a row filled with whitespace and the given attributes, for you to freely use.
|
||||
ROW& TextBuffer::GetScratchpadRow(const TextAttribute& attributes)
|
||||
{
|
||||
// The scratchpad row is mapped to the underlying index 0, whereas all regular rows are mapped to
|
||||
// index 1 and up. We do it this way instead of the other way around (scratchpad row at index _height),
|
||||
// because that would force us to MEM_COMMIT the entire buffer whenever this function is called.
|
||||
auto& r = _getRowByOffsetDirect(0);
|
||||
r.Reset(attributes);
|
||||
return r;
|
||||
@@ -902,15 +907,14 @@ til::point TextBuffer::GetLastNonSpaceCharacter(const Viewport* viewOptional) co
|
||||
|
||||
// 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
|
||||
while (fDoBackUp)
|
||||
|
||||
// while (this row is empty, and we're not at the top)
|
||||
while (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// don't allow negative results
|
||||
@@ -1146,6 +1150,39 @@ void TextBuffer::Reset() noexcept
|
||||
_initialAttributes = _currentAttributes;
|
||||
}
|
||||
|
||||
void TextBuffer::ClearScrollback(const til::CoordType start, const til::CoordType height)
|
||||
{
|
||||
if (start <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (height <= 0)
|
||||
{
|
||||
_decommit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Our goal is to move the viewport to the absolute start of the underlying memory buffer so that we can
|
||||
// MEM_DECOMMIT the remaining memory. _firstRow is used to make the TextBuffer behave like a circular buffer.
|
||||
// The start parameter is relative to the _firstRow. The trick to get the content to the absolute start
|
||||
// is to simply add _firstRow ourselves and then reset it to 0. This causes ScrollRows() to write into
|
||||
// the absolute start while reading from relative coordinates. This works because GetRowByOffset()
|
||||
// operates modulo the buffer height and so the possibly-too-large startAbsolute won't be an issue.
|
||||
const auto startAbsolute = _firstRow + start;
|
||||
_firstRow = 0;
|
||||
ScrollRows(startAbsolute, height, -startAbsolute);
|
||||
|
||||
const auto end = _estimateOffsetOfLastCommittedRow();
|
||||
for (auto y = height; y <= end; ++y)
|
||||
{
|
||||
GetMutableRowByOffset(y).Reset(_initialAttributes);
|
||||
}
|
||||
|
||||
ScrollMarks(-start);
|
||||
ClearMarksInRange(til::point{ 0, height }, til::point{ _width, _height });
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is the legacy screen resize with minimal changes
|
||||
// Arguments:
|
||||
@@ -1916,135 +1953,6 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing).
|
||||
// Arguments:
|
||||
// - includeCRLF - inject CRLF pairs to the end of each line
|
||||
// - trimTrailingWhitespace - remove the trailing whitespace at the end of each line
|
||||
// - textRects - the rectangular regions from which the data will be extracted from the buffer (i.e.: selection rects)
|
||||
// - GetAttributeColors - function used to map TextAttribute to RGB COLORREFs. If null, only extract the text.
|
||||
// - formatWrappedRows - if set we will apply formatting (CRLF inclusion and whitespace trimming) on wrapped rows
|
||||
// Return Value:
|
||||
// - The text, background color, and foreground color data of the selected region of the text buffer.
|
||||
const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<til::inclusive_rect>& selectionRects,
|
||||
std::function<std::pair<COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors,
|
||||
const bool formatWrappedRows) const
|
||||
{
|
||||
TextAndColor data;
|
||||
const auto copyTextColor = GetAttributeColors != nullptr;
|
||||
|
||||
// preallocate our vectors to reduce reallocs
|
||||
const auto rows = selectionRects.size();
|
||||
data.text.reserve(rows);
|
||||
if (copyTextColor)
|
||||
{
|
||||
data.FgAttr.reserve(rows);
|
||||
data.BkAttr.reserve(rows);
|
||||
}
|
||||
|
||||
// for each row in the selection
|
||||
for (size_t i = 0; i < rows; i++)
|
||||
{
|
||||
const auto iRow = selectionRects.at(i).top;
|
||||
|
||||
const auto highlight = Viewport::FromInclusive(selectionRects.at(i));
|
||||
|
||||
// retrieve the data from the screen buffer
|
||||
auto it = GetCellDataAt(highlight.Origin(), highlight);
|
||||
|
||||
// allocate a string buffer
|
||||
std::wstring selectionText;
|
||||
std::vector<COLORREF> selectionFgAttr;
|
||||
std::vector<COLORREF> selectionBkAttr;
|
||||
|
||||
// preallocate to avoid reallocs
|
||||
selectionText.reserve(gsl::narrow<size_t>(highlight.Width()) + 2); // + 2 for \r\n if we munged it
|
||||
if (copyTextColor)
|
||||
{
|
||||
selectionFgAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
|
||||
selectionBkAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
|
||||
}
|
||||
|
||||
// copy char data into the string buffer, skipping trailing bytes
|
||||
while (it)
|
||||
{
|
||||
const auto& cell = *it;
|
||||
|
||||
if (cell.DbcsAttr() != DbcsAttribute::Trailing)
|
||||
{
|
||||
const auto chars = cell.Chars();
|
||||
selectionText.append(chars);
|
||||
|
||||
if (copyTextColor)
|
||||
{
|
||||
const auto cellData = cell.TextAttr();
|
||||
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
|
||||
for (size_t j = 0; j < chars.size(); ++j)
|
||||
{
|
||||
selectionFgAttr.push_back(CellFgAttr);
|
||||
selectionBkAttr.push_back(CellBkAttr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
// We apply formatting to rows if the row was NOT wrapped or formatting of wrapped rows is allowed
|
||||
const auto shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).WasWrapForced();
|
||||
|
||||
if (trimTrailingWhitespace)
|
||||
{
|
||||
if (shouldFormatRow)
|
||||
{
|
||||
// remove the spaces at the end (aka trim the trailing whitespace)
|
||||
while (!selectionText.empty() && selectionText.back() == UNICODE_SPACE)
|
||||
{
|
||||
selectionText.pop_back();
|
||||
if (copyTextColor)
|
||||
{
|
||||
selectionFgAttr.pop_back();
|
||||
selectionBkAttr.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply CR/LF to the end of the final string, unless we're the last line.
|
||||
// a.k.a if we're earlier than the bottom, then apply CR/LF.
|
||||
if (includeCRLF && i < selectionRects.size() - 1)
|
||||
{
|
||||
if (shouldFormatRow)
|
||||
{
|
||||
// then we can assume a CR/LF is proper
|
||||
selectionText.push_back(UNICODE_CARRIAGERETURN);
|
||||
selectionText.push_back(UNICODE_LINEFEED);
|
||||
|
||||
if (copyTextColor)
|
||||
{
|
||||
// can't see CR/LF so just use black FG & BK
|
||||
const auto Blackness = RGB(0x00, 0x00, 0x00);
|
||||
selectionFgAttr.push_back(Blackness);
|
||||
selectionFgAttr.push_back(Blackness);
|
||||
selectionBkAttr.push_back(Blackness);
|
||||
selectionBkAttr.push_back(Blackness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.text.emplace_back(std::move(selectionText));
|
||||
if (copyTextColor)
|
||||
{
|
||||
data.FgAttr.emplace_back(std::move(selectionFgAttr));
|
||||
data.BkAttr.emplace_back(std::move(selectionBkAttr));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
size_t TextBuffer::SpanLength(const til::point coordStart, const til::point coordEnd) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
@@ -2083,186 +1991,292 @@ std::wstring TextBuffer::GetPlainText(const til::point& start, const til::point&
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Generates a CF_HTML compliant structure based on the passed in text and color data
|
||||
// - Given a copy request and a row, retrieves the row bounds [begin, end) and
|
||||
// a boolean indicating whether a line break should be added to this row.
|
||||
// Arguments:
|
||||
// - rows - the text and color data we will format & encapsulate
|
||||
// - backgroundColor - default background color for characters, also used in padding
|
||||
// - req - the copy request
|
||||
// - iRow - the row index
|
||||
// - row - the row
|
||||
// Return Value:
|
||||
// - The row bounds and a boolean for line break
|
||||
std::tuple<til::CoordType, til::CoordType, bool> TextBuffer::_RowCopyHelper(const TextBuffer::CopyRequest& req, const til::CoordType iRow, const ROW& row) const
|
||||
{
|
||||
til::CoordType rowBeg = 0;
|
||||
til::CoordType rowEnd = 0;
|
||||
if (req.blockSelection)
|
||||
{
|
||||
const auto lineRendition = row.GetLineRendition();
|
||||
const auto minX = req.bufferCoordinates ? req.minX : ScreenToBufferLine(til::point{ req.minX, iRow }, lineRendition).x;
|
||||
const auto maxX = req.bufferCoordinates ? req.maxX : ScreenToBufferLine(til::point{ req.maxX, iRow }, lineRendition).x;
|
||||
|
||||
rowBeg = minX;
|
||||
rowEnd = maxX + 1; // +1 to get an exclusive end
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto lineRendition = row.GetLineRendition();
|
||||
const auto beg = req.bufferCoordinates ? req.beg : ScreenToBufferLine(req.beg, lineRendition);
|
||||
const auto end = req.bufferCoordinates ? req.end : ScreenToBufferLine(req.end, lineRendition);
|
||||
|
||||
rowBeg = iRow != beg.y ? 0 : beg.x;
|
||||
rowEnd = iRow != end.y ? row.GetReadableColumnCount() : end.x + 1; // +1 to get an exclusive end
|
||||
}
|
||||
|
||||
// Our selection mechanism doesn't stick to glyph boundaries at the moment.
|
||||
// We need to adjust begin and end points manually to avoid partially
|
||||
// selected glyphs.
|
||||
rowBeg = row.AdjustToGlyphStart(rowBeg);
|
||||
rowEnd = row.AdjustToGlyphEnd(rowEnd);
|
||||
|
||||
// When `formatWrappedRows` is set, apply formatting on all rows (wrapped
|
||||
// and non-wrapped), but when it's false, format non-wrapped rows only.
|
||||
const auto shouldFormatRow = req.formatWrappedRows || !row.WasWrapForced();
|
||||
|
||||
// trim trailing whitespace
|
||||
if (shouldFormatRow && req.trimTrailingWhitespace)
|
||||
{
|
||||
rowEnd = std::min(rowEnd, row.GetLastNonSpaceColumn());
|
||||
}
|
||||
|
||||
// line breaks
|
||||
const auto addLineBreak = shouldFormatRow && req.includeLineBreak;
|
||||
|
||||
return { rowBeg, rowEnd, addLineBreak };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the text data from the buffer and presents it in a clipboard-ready format.
|
||||
// Arguments:
|
||||
// - req - the copy request having the bounds of the selected region and other related configuration flags.
|
||||
// Return Value:
|
||||
// - The text data from the selected region of the text buffer. Empty if the copy request is invalid.
|
||||
std::wstring TextBuffer::GetPlainText(const CopyRequest& req) const
|
||||
{
|
||||
if (req.beg > req.end)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring selectedText;
|
||||
|
||||
for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow)
|
||||
{
|
||||
const auto& row = GetRowByOffset(iRow);
|
||||
const auto& [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row);
|
||||
|
||||
// save selected text
|
||||
selectedText += row.GetText(rowBeg, rowEnd);
|
||||
|
||||
if (addLineBreak && iRow != req.end.y)
|
||||
{
|
||||
selectedText += L"\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
return selectedText;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Generates a CF_HTML compliant structure from the selected region of the buffer
|
||||
// Arguments:
|
||||
// - req - the copy request having the bounds of the selected region and other related configuration flags.
|
||||
// - fontHeightPoints - the unscaled font height
|
||||
// - fontFaceName - the name of the font used
|
||||
// - backgroundColor - default background color for characters, also used in padding
|
||||
// - isIntenseBold - true if being intense is treated as being bold
|
||||
// - GetAttributeColors - function to get the colors of the text attributes as they're rendered
|
||||
// Return Value:
|
||||
// - string containing the generated HTML
|
||||
std::string TextBuffer::GenHTML(const TextAndColor& rows,
|
||||
// - string containing the generated HTML. Empty if the copy request is invalid.
|
||||
std::string TextBuffer::GenHTML(const CopyRequest& req,
|
||||
const int fontHeightPoints,
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor)
|
||||
const COLORREF backgroundColor,
|
||||
const bool isIntenseBold,
|
||||
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept
|
||||
{
|
||||
// GH#5347 - Don't provide a title for the generated HTML, as many
|
||||
// web applications will paste the title first, followed by the HTML
|
||||
// content, which is unexpected.
|
||||
|
||||
if (req.beg > req.end)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::ostringstream htmlBuilder;
|
||||
std::string htmlBuilder;
|
||||
|
||||
// First we have to add some standard
|
||||
// HTML boiler plate required for CF_HTML
|
||||
// as part of the HTML Clipboard format
|
||||
const std::string htmlHeader =
|
||||
"<!DOCTYPE><HTML><HEAD></HEAD><BODY>";
|
||||
htmlBuilder << htmlHeader;
|
||||
// First we have to add some standard HTML boiler plate required for
|
||||
// CF_HTML as part of the HTML Clipboard format
|
||||
constexpr std::string_view htmlHeader = "<!DOCTYPE><HTML><HEAD></HEAD><BODY>";
|
||||
htmlBuilder += htmlHeader;
|
||||
|
||||
htmlBuilder << "<!--StartFragment -->";
|
||||
htmlBuilder += "<!--StartFragment -->";
|
||||
|
||||
// apply global style in div element
|
||||
{
|
||||
htmlBuilder << "<DIV STYLE=\"";
|
||||
htmlBuilder << "display:inline-block;";
|
||||
htmlBuilder << "white-space:pre;";
|
||||
htmlBuilder += "<DIV STYLE=\"";
|
||||
htmlBuilder += "display:inline-block;";
|
||||
htmlBuilder += "white-space:pre;";
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("background-color:{};"), Utils::ColorToHexString(backgroundColor));
|
||||
|
||||
htmlBuilder << "background-color:";
|
||||
htmlBuilder << Utils::ColorToHexString(backgroundColor);
|
||||
htmlBuilder << ";";
|
||||
|
||||
htmlBuilder << "font-family:";
|
||||
htmlBuilder << "'";
|
||||
htmlBuilder << ConvertToA(CP_UTF8, fontFaceName);
|
||||
htmlBuilder << "',";
|
||||
// even with different font, add monospace as fallback
|
||||
htmlBuilder << "monospace;";
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("font-family:'{}',monospace;"), til::u16u8(fontFaceName));
|
||||
|
||||
htmlBuilder << "font-size:";
|
||||
htmlBuilder << fontHeightPoints;
|
||||
htmlBuilder << "pt;";
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("font-size:{}pt;"), fontHeightPoints);
|
||||
|
||||
// note: MS Word doesn't support padding (in this way at least)
|
||||
htmlBuilder << "padding:";
|
||||
htmlBuilder << 4; // todo: customizable padding
|
||||
htmlBuilder << "px;";
|
||||
// todo: customizable padding
|
||||
htmlBuilder += "padding:4px;";
|
||||
|
||||
htmlBuilder << "\">";
|
||||
htmlBuilder += "\">";
|
||||
}
|
||||
|
||||
// copy text and info color from buffer
|
||||
auto hasWrittenAnyText = false;
|
||||
std::optional<COLORREF> fgColor = std::nullopt;
|
||||
std::optional<COLORREF> bkColor = std::nullopt;
|
||||
for (size_t row = 0; row < rows.text.size(); row++)
|
||||
for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow)
|
||||
{
|
||||
size_t startOffset = 0;
|
||||
const auto& row = GetRowByOffset(iRow);
|
||||
const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row);
|
||||
const auto rowBegU16 = gsl::narrow_cast<uint16_t>(rowBeg);
|
||||
const auto rowEndU16 = gsl::narrow_cast<uint16_t>(rowEnd);
|
||||
const auto runs = row.Attributes().slice(rowBegU16, rowEndU16).runs();
|
||||
|
||||
if (row != 0)
|
||||
auto x = rowBegU16;
|
||||
for (const auto& [attr, length] : runs)
|
||||
{
|
||||
htmlBuilder << "<BR>";
|
||||
const auto nextX = gsl::narrow_cast<uint16_t>(x + length);
|
||||
const auto [fg, bg, ul] = GetAttributeColors(attr);
|
||||
const auto fgHex = Utils::ColorToHexString(fg);
|
||||
const auto bgHex = Utils::ColorToHexString(bg);
|
||||
const auto ulHex = Utils::ColorToHexString(ul);
|
||||
const auto ulStyle = attr.GetUnderlineStyle();
|
||||
const auto isUnderlined = ulStyle != UnderlineStyle::NoUnderline;
|
||||
const auto isCrossedOut = attr.IsCrossedOut();
|
||||
const auto isOverlined = attr.IsOverlined();
|
||||
|
||||
htmlBuilder += "<SPAN STYLE=\"";
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("color:{};"), fgHex);
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("background-color:{};"), bgHex);
|
||||
|
||||
if (isIntenseBold && attr.IsIntense())
|
||||
{
|
||||
htmlBuilder += "font-weight:bold;";
|
||||
}
|
||||
|
||||
if (attr.IsItalic())
|
||||
{
|
||||
htmlBuilder += "font-style:italic;";
|
||||
}
|
||||
|
||||
if (isCrossedOut || isOverlined)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(htmlBuilder),
|
||||
FMT_COMPILE("text-decoration:{} {} {};"),
|
||||
isCrossedOut ? "line-through" : "",
|
||||
isOverlined ? "overline" : "",
|
||||
fgHex);
|
||||
}
|
||||
|
||||
if (isUnderlined)
|
||||
{
|
||||
// Since underline, overline and strikethrough use the same css property,
|
||||
// we cannot apply different colors to them at the same time. However, we
|
||||
// can achieve the desired result by creating a nested <span> and applying
|
||||
// underline style and color to it.
|
||||
htmlBuilder += "\"><SPAN STYLE=\"";
|
||||
|
||||
switch (ulStyle)
|
||||
{
|
||||
case UnderlineStyle::NoUnderline:
|
||||
break;
|
||||
case UnderlineStyle::DoublyUnderlined:
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline double {};"), ulHex);
|
||||
break;
|
||||
case UnderlineStyle::CurlyUnderlined:
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline wavy {};"), ulHex);
|
||||
break;
|
||||
case UnderlineStyle::DottedUnderlined:
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline dotted {};"), ulHex);
|
||||
break;
|
||||
case UnderlineStyle::DashedUnderlined:
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline dashed {};"), ulHex);
|
||||
break;
|
||||
case UnderlineStyle::SinglyUnderlined:
|
||||
default:
|
||||
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline {};"), ulHex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
htmlBuilder += "\">";
|
||||
|
||||
// text
|
||||
std::string unescapedText;
|
||||
THROW_IF_FAILED(til::u16u8(row.GetText(x, nextX), unescapedText));
|
||||
for (const auto c : unescapedText)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '<':
|
||||
htmlBuilder += "<";
|
||||
break;
|
||||
case '>':
|
||||
htmlBuilder += ">";
|
||||
break;
|
||||
case '&':
|
||||
htmlBuilder += "&";
|
||||
break;
|
||||
default:
|
||||
htmlBuilder += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUnderlined)
|
||||
{
|
||||
// close the nested span we created for underline
|
||||
htmlBuilder += "</SPAN>";
|
||||
}
|
||||
|
||||
htmlBuilder += "</SPAN>";
|
||||
|
||||
// advance to next run of text
|
||||
x = nextX;
|
||||
}
|
||||
|
||||
for (size_t col = 0; col < rows.text.at(row).length(); col++)
|
||||
// never add line break to the last row.
|
||||
if (addLineBreak && iRow < req.end.y)
|
||||
{
|
||||
const auto writeAccumulatedChars = [&](bool includeCurrent) {
|
||||
if (col >= startOffset)
|
||||
{
|
||||
const auto unescapedText = ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent));
|
||||
for (const auto c : unescapedText)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '<':
|
||||
htmlBuilder << "<";
|
||||
break;
|
||||
case '>':
|
||||
htmlBuilder << ">";
|
||||
break;
|
||||
case '&':
|
||||
htmlBuilder << "&";
|
||||
break;
|
||||
default:
|
||||
htmlBuilder << c;
|
||||
}
|
||||
}
|
||||
|
||||
startOffset = col;
|
||||
}
|
||||
};
|
||||
|
||||
if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n')
|
||||
{
|
||||
// do not include \r nor \n as they don't have color attributes
|
||||
// and are not HTML friendly. For line break use '<BR>' instead.
|
||||
writeAccumulatedChars(false);
|
||||
break;
|
||||
}
|
||||
|
||||
auto colorChanged = false;
|
||||
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
|
||||
{
|
||||
fgColor = rows.FgAttr.at(row).at(col);
|
||||
colorChanged = true;
|
||||
}
|
||||
|
||||
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
|
||||
{
|
||||
bkColor = rows.BkAttr.at(row).at(col);
|
||||
colorChanged = true;
|
||||
}
|
||||
|
||||
if (colorChanged)
|
||||
{
|
||||
writeAccumulatedChars(false);
|
||||
|
||||
if (hasWrittenAnyText)
|
||||
{
|
||||
htmlBuilder << "</SPAN>";
|
||||
}
|
||||
|
||||
htmlBuilder << "<SPAN STYLE=\"";
|
||||
htmlBuilder << "color:";
|
||||
htmlBuilder << Utils::ColorToHexString(fgColor.value());
|
||||
htmlBuilder << ";";
|
||||
htmlBuilder << "background-color:";
|
||||
htmlBuilder << Utils::ColorToHexString(bkColor.value());
|
||||
htmlBuilder << ";";
|
||||
htmlBuilder << "\">";
|
||||
}
|
||||
|
||||
hasWrittenAnyText = true;
|
||||
|
||||
// if this is the last character in the row, flush the whole row
|
||||
if (col == rows.text.at(row).length() - 1)
|
||||
{
|
||||
writeAccumulatedChars(true);
|
||||
}
|
||||
htmlBuilder += "<BR>";
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWrittenAnyText)
|
||||
{
|
||||
// last opened span wasn't closed in loop above, so close it now
|
||||
htmlBuilder << "</SPAN>";
|
||||
}
|
||||
htmlBuilder += "</DIV>";
|
||||
|
||||
htmlBuilder << "</DIV>";
|
||||
|
||||
htmlBuilder << "<!--EndFragment -->";
|
||||
htmlBuilder += "<!--EndFragment -->";
|
||||
|
||||
constexpr std::string_view HtmlFooter = "</BODY></HTML>";
|
||||
htmlBuilder << HtmlFooter;
|
||||
htmlBuilder += HtmlFooter;
|
||||
|
||||
// once filled with values, there will be exactly 157 bytes in the clipboard header
|
||||
constexpr size_t ClipboardHeaderSize = 157;
|
||||
|
||||
// these values are byte offsets from start of clipboard
|
||||
const auto htmlStartPos = ClipboardHeaderSize;
|
||||
const auto htmlEndPos = ClipboardHeaderSize + gsl::narrow<size_t>(htmlBuilder.tellp());
|
||||
const auto htmlEndPos = ClipboardHeaderSize + gsl::narrow<size_t>(htmlBuilder.length());
|
||||
const auto fragStartPos = ClipboardHeaderSize + gsl::narrow<size_t>(htmlHeader.length());
|
||||
const auto fragEndPos = htmlEndPos - HtmlFooter.length();
|
||||
|
||||
// header required by HTML 0.9 format
|
||||
std::ostringstream clipHeaderBuilder;
|
||||
clipHeaderBuilder << "Version:0.9\r\n";
|
||||
clipHeaderBuilder << std::setfill('0');
|
||||
clipHeaderBuilder << "StartHTML:" << std::setw(10) << htmlStartPos << "\r\n";
|
||||
clipHeaderBuilder << "EndHTML:" << std::setw(10) << htmlEndPos << "\r\n";
|
||||
clipHeaderBuilder << "StartFragment:" << std::setw(10) << fragStartPos << "\r\n";
|
||||
clipHeaderBuilder << "EndFragment:" << std::setw(10) << fragEndPos << "\r\n";
|
||||
clipHeaderBuilder << "StartSelection:" << std::setw(10) << fragStartPos << "\r\n";
|
||||
clipHeaderBuilder << "EndSelection:" << std::setw(10) << fragEndPos << "\r\n";
|
||||
std::string clipHeaderBuilder;
|
||||
clipHeaderBuilder += "Version:0.9\r\n";
|
||||
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartHTML:{:0>10}\r\n"), htmlStartPos);
|
||||
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndHTML:{:0>10}\r\n"), htmlEndPos);
|
||||
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartFragment:{:0>10}\r\n"), fragStartPos);
|
||||
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndFragment:{:0>10}\r\n"), fragEndPos);
|
||||
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartSelection:{:0>10}\r\n"), fragStartPos);
|
||||
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndSelection:{:0>10}\r\n"), fragEndPos);
|
||||
|
||||
return clipHeaderBuilder.str() + htmlBuilder.str();
|
||||
return clipHeaderBuilder + htmlBuilder;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -2272,25 +2286,36 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows,
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Generates an RTF document based on the passed in text and color data
|
||||
// - Generates an RTF document from the selected region of the buffer
|
||||
// RTF 1.5 Spec: https://www.biblioscape.com/rtf15_spec.htm
|
||||
// RTF 1.9.1 Spec: https://msopenspecs.azureedge.net/files/Archive_References/[MSFT-RTF].pdf
|
||||
// Arguments:
|
||||
// - rows - the text and color data we will format & encapsulate
|
||||
// - backgroundColor - default background color for characters, also used in padding
|
||||
// - req - the copy request having the bounds of the selected region and other related configuration flags.
|
||||
// - fontHeightPoints - the unscaled font height
|
||||
// - fontFaceName - the name of the font used
|
||||
// - htmlTitle - value used in title tag of html header. Used to name the application
|
||||
// - backgroundColor - default background color for characters, also used in padding
|
||||
// - isIntenseBold - true if being intense is treated as being bold
|
||||
// - GetAttributeColors - function to get the colors of the text attributes as they're rendered
|
||||
// Return Value:
|
||||
// - string containing the generated RTF
|
||||
std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoints, const std::wstring_view fontFaceName, const COLORREF backgroundColor)
|
||||
// - string containing the generated RTF. Empty if the copy request is invalid.
|
||||
std::string TextBuffer::GenRTF(const CopyRequest& req,
|
||||
const int fontHeightPoints,
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor,
|
||||
const bool isIntenseBold,
|
||||
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept
|
||||
{
|
||||
if (req.beg > req.end)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::ostringstream rtfBuilder;
|
||||
std::string rtfBuilder;
|
||||
|
||||
// start rtf
|
||||
rtfBuilder << "{";
|
||||
rtfBuilder += "{";
|
||||
|
||||
// Standard RTF header.
|
||||
// This is similar to the header generated by WordPad.
|
||||
@@ -2306,10 +2331,11 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
||||
// Some features are blocked by default to maintain compatibility
|
||||
// with older programs (Eg. Word 97-2003). `nouicompat` disables this
|
||||
// behavior, and unblocks these features. See: Spec 1.9.1, Pg. 51.
|
||||
rtfBuilder << "\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat";
|
||||
rtfBuilder += "\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat";
|
||||
|
||||
// font table
|
||||
rtfBuilder << "{\\fonttbl{\\f0\\fmodern\\fcharset0 " << ConvertToA(CP_UTF8, fontFaceName) << ";}}";
|
||||
// Brace escape: add an extra brace (of same kind) after a brace to escape it within the format string.
|
||||
fmt::format_to(std::back_inserter(rtfBuilder), FMT_COMPILE("{{\\fonttbl{{\\f0\\fmodern\\fcharset0 {};}}}}"), til::u16u8(fontFaceName));
|
||||
|
||||
// map to keep track of colors:
|
||||
// keys are colors represented by COLORREF
|
||||
@@ -2317,8 +2343,8 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
||||
std::unordered_map<COLORREF, size_t> colorMap;
|
||||
|
||||
// RTF color table
|
||||
std::ostringstream colorTableBuilder;
|
||||
colorTableBuilder << "{\\colortbl ;";
|
||||
std::string colorTableBuilder;
|
||||
colorTableBuilder += "{\\colortbl ;";
|
||||
|
||||
const auto getColorTableIndex = [&](const COLORREF color) -> size_t {
|
||||
// Exclude the 0 index for the default color, and start with 1.
|
||||
@@ -2326,103 +2352,127 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
||||
const auto [it, inserted] = colorMap.emplace(color, colorMap.size() + 1);
|
||||
if (inserted)
|
||||
{
|
||||
colorTableBuilder << "\\red" << static_cast<int>(GetRValue(color))
|
||||
<< "\\green" << static_cast<int>(GetGValue(color))
|
||||
<< "\\blue" << static_cast<int>(GetBValue(color))
|
||||
<< ";";
|
||||
const auto red = static_cast<int>(GetRValue(color));
|
||||
const auto green = static_cast<int>(GetGValue(color));
|
||||
const auto blue = static_cast<int>(GetBValue(color));
|
||||
fmt::format_to(std::back_inserter(colorTableBuilder), FMT_COMPILE("\\red{}\\green{}\\blue{};"), red, green, blue);
|
||||
}
|
||||
return it->second;
|
||||
};
|
||||
|
||||
// content
|
||||
std::ostringstream contentBuilder;
|
||||
contentBuilder << "\\viewkind4\\uc4";
|
||||
std::string contentBuilder;
|
||||
|
||||
// \viewkindN: View mode of the document to be used. N=4 specifies that the document is in Normal view. (maybe unnecessary?)
|
||||
// \ucN: Number of unicode fallback characters after each codepoint. (global)
|
||||
contentBuilder += "\\viewkind4\\uc1";
|
||||
|
||||
// paragraph styles
|
||||
// \fs specifies font size in half-points i.e. \fs20 results in a font size
|
||||
// of 10 pts. That's why, font size is multiplied by 2 here.
|
||||
contentBuilder << "\\pard\\slmult1\\f0\\fs" << std::to_string(2 * fontHeightPoints)
|
||||
// Set the background color for the page. But, the
|
||||
// standard way (\cbN) to do this isn't supported in Word.
|
||||
// However, the following control words sequence works
|
||||
// in Word (and other RTF editors also) for applying the
|
||||
// text background color. See: Spec 1.9.1, Pg. 23.
|
||||
<< "\\chshdng0\\chcbpat" << getColorTableIndex(backgroundColor)
|
||||
<< " ";
|
||||
// \pard: paragraph description
|
||||
// \slmultN: line-spacing multiple
|
||||
// \fN: font to be used for the paragraph, where N is the font index in the font table
|
||||
contentBuilder += "\\pard\\slmult1\\f0";
|
||||
|
||||
std::optional<COLORREF> fgColor = std::nullopt;
|
||||
std::optional<COLORREF> bkColor = std::nullopt;
|
||||
for (size_t row = 0; row < rows.text.size(); ++row)
|
||||
// \fsN: specifies font size in half-points. E.g. \fs20 results in a font
|
||||
// size of 10 pts. That's why, font size is multiplied by 2 here.
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\fs{}"), std::to_string(2 * fontHeightPoints));
|
||||
|
||||
// Set the background color for the page. But the standard way (\cbN) to do
|
||||
// this isn't supported in Word. However, the following control words sequence
|
||||
// works in Word (and other RTF editors also) for applying the text background
|
||||
// color. See: Spec 1.9.1, Pg. 23.
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\chshdng0\\chcbpat{}"), getColorTableIndex(backgroundColor));
|
||||
|
||||
for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow)
|
||||
{
|
||||
size_t startOffset = 0;
|
||||
const auto& row = GetRowByOffset(iRow);
|
||||
const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row);
|
||||
const auto rowBegU16 = gsl::narrow_cast<uint16_t>(rowBeg);
|
||||
const auto rowEndU16 = gsl::narrow_cast<uint16_t>(rowEnd);
|
||||
const auto runs = row.Attributes().slice(rowBegU16, rowEndU16).runs();
|
||||
|
||||
if (row != 0)
|
||||
auto x = rowBegU16;
|
||||
for (auto& [attr, length] : runs)
|
||||
{
|
||||
contentBuilder << "\\line "; // new line
|
||||
}
|
||||
const auto nextX = gsl::narrow_cast<uint16_t>(x + length);
|
||||
const auto [fg, bg, ul] = GetAttributeColors(attr);
|
||||
const auto fgIdx = getColorTableIndex(fg);
|
||||
const auto bgIdx = getColorTableIndex(bg);
|
||||
const auto ulIdx = getColorTableIndex(ul);
|
||||
const auto ulStyle = attr.GetUnderlineStyle();
|
||||
|
||||
for (size_t col = 0; col < rows.text.at(row).length(); ++col)
|
||||
{
|
||||
const auto writeAccumulatedChars = [&](bool includeCurrent) {
|
||||
if (col >= startOffset)
|
||||
{
|
||||
const auto text = std::wstring_view{ rows.text.at(row) }.substr(startOffset, col - startOffset + includeCurrent);
|
||||
_AppendRTFText(contentBuilder, text);
|
||||
// start an RTF group that can be closed later to restore the
|
||||
// default attribute.
|
||||
contentBuilder += "{";
|
||||
|
||||
startOffset = col;
|
||||
}
|
||||
};
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\cf{}"), fgIdx);
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\chshdng0\\chcbpat{}"), bgIdx);
|
||||
|
||||
if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n')
|
||||
if (isIntenseBold && attr.IsIntense())
|
||||
{
|
||||
// do not include \r nor \n as they don't have color attributes.
|
||||
// For line break use \line instead.
|
||||
writeAccumulatedChars(false);
|
||||
contentBuilder += "\\b";
|
||||
}
|
||||
|
||||
if (attr.IsItalic())
|
||||
{
|
||||
contentBuilder += "\\i";
|
||||
}
|
||||
|
||||
if (attr.IsCrossedOut())
|
||||
{
|
||||
contentBuilder += "\\strike";
|
||||
}
|
||||
|
||||
switch (ulStyle)
|
||||
{
|
||||
case UnderlineStyle::NoUnderline:
|
||||
break;
|
||||
case UnderlineStyle::DoublyUnderlined:
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uldb\\ulc{}"), ulIdx);
|
||||
break;
|
||||
case UnderlineStyle::CurlyUnderlined:
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\ulwave\\ulc{}"), ulIdx);
|
||||
break;
|
||||
case UnderlineStyle::DottedUnderlined:
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uld\\ulc{}"), ulIdx);
|
||||
break;
|
||||
case UnderlineStyle::DashedUnderlined:
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uldash\\ulc{}"), ulIdx);
|
||||
break;
|
||||
case UnderlineStyle::SinglyUnderlined:
|
||||
default:
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\ul\\ulc{}"), ulIdx);
|
||||
break;
|
||||
}
|
||||
|
||||
auto colorChanged = false;
|
||||
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
|
||||
{
|
||||
fgColor = rows.FgAttr.at(row).at(col);
|
||||
colorChanged = true;
|
||||
}
|
||||
// RTF commands and the text data must be separated by a space.
|
||||
// Otherwise, if the text begins with a space then that space will
|
||||
// be interpreted as part of the last command, and will be lost.
|
||||
contentBuilder += " ";
|
||||
|
||||
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
|
||||
{
|
||||
bkColor = rows.BkAttr.at(row).at(col);
|
||||
colorChanged = true;
|
||||
}
|
||||
const auto unescapedText = row.GetText(x, nextX); // including character at nextX
|
||||
_AppendRTFText(contentBuilder, unescapedText);
|
||||
|
||||
if (colorChanged)
|
||||
{
|
||||
writeAccumulatedChars(false);
|
||||
contentBuilder << "\\chshdng0\\chcbpat" << getColorTableIndex(bkColor.value())
|
||||
<< "\\cf" << getColorTableIndex(fgColor.value())
|
||||
<< " ";
|
||||
}
|
||||
contentBuilder += "}"; // close RTF group
|
||||
|
||||
// if this is the last character in the row, flush the whole row
|
||||
if (col == rows.text.at(row).length() - 1)
|
||||
{
|
||||
writeAccumulatedChars(true);
|
||||
}
|
||||
// advance to next run of text
|
||||
x = nextX;
|
||||
}
|
||||
|
||||
// never add line break to the last row.
|
||||
if (addLineBreak && iRow < req.end.y)
|
||||
{
|
||||
contentBuilder += "\\line";
|
||||
}
|
||||
}
|
||||
|
||||
// end colortbl
|
||||
colorTableBuilder << "}";
|
||||
|
||||
// add color table to the final RTF
|
||||
rtfBuilder << colorTableBuilder.str();
|
||||
rtfBuilder += colorTableBuilder + "}";
|
||||
|
||||
// add the text content to the final RTF
|
||||
rtfBuilder << contentBuilder.str();
|
||||
rtfBuilder += contentBuilder + "}";
|
||||
|
||||
// end rtf
|
||||
rtfBuilder << "}";
|
||||
|
||||
return rtfBuilder.str();
|
||||
return rtfBuilder;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -2431,7 +2481,7 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
||||
}
|
||||
}
|
||||
|
||||
void TextBuffer::_AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text)
|
||||
void TextBuffer::_AppendRTFText(std::string& contentBuilder, const std::wstring_view& text)
|
||||
{
|
||||
for (const auto codeUnit : text)
|
||||
{
|
||||
@@ -2442,16 +2492,18 @@ void TextBuffer::_AppendRTFText(std::ostringstream& contentBuilder, const std::w
|
||||
case L'\\':
|
||||
case L'{':
|
||||
case L'}':
|
||||
contentBuilder << "\\" << gsl::narrow<char>(codeUnit);
|
||||
break;
|
||||
contentBuilder += "\\";
|
||||
[[fallthrough]];
|
||||
default:
|
||||
contentBuilder << gsl::narrow<char>(codeUnit);
|
||||
contentBuilder += gsl::narrow_cast<char>(codeUnit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Windows uses unsigned wchar_t - RTF uses signed ones.
|
||||
contentBuilder << "\\u" << std::to_string(til::bit_cast<int16_t>(codeUnit)) << "?";
|
||||
// '?' is the fallback ascii character.
|
||||
const auto codeUnitRTFStr = std::to_string(til::bit_cast<int16_t>(codeUnit));
|
||||
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\u{}?"), codeUnitRTFStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +194,7 @@ public:
|
||||
til::point BufferToScreenPosition(const til::point position) const;
|
||||
|
||||
void Reset() noexcept;
|
||||
void ClearScrollback(const til::CoordType start, const til::CoordType height);
|
||||
|
||||
void ResizeTraditional(const til::size newSize);
|
||||
|
||||
@@ -229,33 +230,94 @@ public:
|
||||
std::wstring GetCustomIdFromId(uint16_t id) const;
|
||||
void CopyHyperlinkMaps(const TextBuffer& OtherBuffer);
|
||||
|
||||
class TextAndColor
|
||||
{
|
||||
public:
|
||||
std::vector<std::wstring> text;
|
||||
std::vector<std::vector<COLORREF>> FgAttr;
|
||||
std::vector<std::vector<COLORREF>> BkAttr;
|
||||
};
|
||||
|
||||
size_t SpanLength(const til::point coordStart, const til::point coordEnd) const;
|
||||
|
||||
const TextAndColor GetText(const bool includeCRLF,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<til::inclusive_rect>& textRects,
|
||||
std::function<std::pair<COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors = nullptr,
|
||||
const bool formatWrappedRows = false) const;
|
||||
|
||||
std::wstring GetPlainText(const til::point& start, const til::point& end) const;
|
||||
|
||||
static std::string GenHTML(const TextAndColor& rows,
|
||||
const int fontHeightPoints,
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor);
|
||||
struct CopyRequest
|
||||
{
|
||||
// beg and end coordinates are inclusive
|
||||
til::point beg;
|
||||
til::point end;
|
||||
|
||||
static std::string GenRTF(const TextAndColor& rows,
|
||||
const int fontHeightPoints,
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor);
|
||||
til::CoordType minX;
|
||||
til::CoordType maxX;
|
||||
bool blockSelection = false;
|
||||
bool trimTrailingWhitespace = true;
|
||||
bool includeLineBreak = true;
|
||||
bool formatWrappedRows = false;
|
||||
|
||||
// whether beg, end coordinates are in buffer coordinates or screen coordinates
|
||||
bool bufferCoordinates = false;
|
||||
|
||||
CopyRequest() = default;
|
||||
|
||||
constexpr CopyRequest(const TextBuffer& buffer, const til::point& beg, const til::point& end, const bool blockSelection, const bool includeLineBreak, const bool trimTrailingWhitespace, const bool formatWrappedRows, const bool bufferCoordinates = false) noexcept :
|
||||
beg{ std::max(beg, til::point{ 0, 0 }) },
|
||||
end{ std::min(end, til::point{ buffer._width - 1, buffer._height - 1 }) },
|
||||
minX{ std::min(this->beg.x, this->end.x) },
|
||||
maxX{ std::max(this->beg.x, this->end.x) },
|
||||
blockSelection{ blockSelection },
|
||||
includeLineBreak{ includeLineBreak },
|
||||
trimTrailingWhitespace{ trimTrailingWhitespace },
|
||||
formatWrappedRows{ formatWrappedRows },
|
||||
bufferCoordinates{ bufferCoordinates }
|
||||
{
|
||||
}
|
||||
|
||||
static CopyRequest FromConfig(const TextBuffer& buffer,
|
||||
const til::point& beg,
|
||||
const til::point& end,
|
||||
const bool singleLine,
|
||||
const bool blockSelection,
|
||||
const bool trimBlockSelection,
|
||||
const bool bufferCoordinates = false) noexcept
|
||||
{
|
||||
return {
|
||||
buffer,
|
||||
beg,
|
||||
end,
|
||||
blockSelection,
|
||||
|
||||
/* includeLineBreak */
|
||||
// - SingleLine mode collapses all rows into one line, unless we're in
|
||||
// block selection mode.
|
||||
// - Block selection should preserve the visual structure by including
|
||||
// line breaks on all rows (together with `formatWrappedRows`).
|
||||
// (Selects like a box, pastes like a box)
|
||||
!singleLine || blockSelection,
|
||||
|
||||
/* trimTrailingWhitespace */
|
||||
// Trim trailing whitespace if we're not in single line mode and — either
|
||||
// we're not in block selection mode or, we're in block selection mode and
|
||||
// trimming is allowed.
|
||||
!singleLine && (!blockSelection || trimBlockSelection),
|
||||
|
||||
/* formatWrappedRows */
|
||||
// In block selection, we should apply formatting to wrapped rows as well.
|
||||
// (Otherwise, they're only applied to non-wrapped rows.)
|
||||
blockSelection,
|
||||
|
||||
bufferCoordinates
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
std::wstring GetPlainText(const CopyRequest& req) const;
|
||||
|
||||
std::string GenHTML(const CopyRequest& req,
|
||||
const int fontHeightPoints,
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor,
|
||||
const bool isIntenseBold,
|
||||
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept;
|
||||
|
||||
std::string GenRTF(const CopyRequest& req,
|
||||
const int fontHeightPoints,
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor,
|
||||
const bool isIntenseBold,
|
||||
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept;
|
||||
|
||||
struct PositionInformation
|
||||
{
|
||||
@@ -303,8 +365,9 @@ private:
|
||||
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||
void _PruneHyperlinks();
|
||||
void _trimMarksOutsideBuffer();
|
||||
std::tuple<til::CoordType, til::CoordType, bool> _RowCopyHelper(const CopyRequest& req, const til::CoordType iRow, const ROW& row) const;
|
||||
|
||||
static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);
|
||||
static void _AppendRTFText(std::string& contentBuilder, const std::wstring_view& text);
|
||||
|
||||
Microsoft::Console::Render::Renderer& _renderer;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ClCompile Include="ReflowTests.cpp" />
|
||||
<ClCompile Include="TextColorTests.cpp" />
|
||||
<ClCompile Include="TextAttributeTests.cpp" />
|
||||
<ClCompile Include="UTextAdapterTests.cpp" />
|
||||
<ClCompile Include="precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -41,4 +42,4 @@
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.build.tests.props" />
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
63
src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp
Normal file
63
src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "WexTestClass.h"
|
||||
#include "../textBuffer.hpp"
|
||||
#include "../../renderer/inc/DummyRenderer.hpp"
|
||||
|
||||
template<>
|
||||
class WEX::TestExecution::VerifyOutputTraits<std::vector<til::point_span>>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const std::vector<til::point_span>& vec)
|
||||
{
|
||||
WEX::Common::NoThrowString str;
|
||||
str.Append(L"{ ");
|
||||
for (size_t i = 0; i < vec.size(); ++i)
|
||||
{
|
||||
const auto& s = vec[i];
|
||||
if (i != 0)
|
||||
{
|
||||
str.Append(L", ");
|
||||
}
|
||||
str.AppendFormat(L"{(%d, %d), (%d, %d)}", s.start.x, s.start.y, s.end.x, s.end.y);
|
||||
}
|
||||
str.Append(L" }");
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
class UTextAdapterTests
|
||||
{
|
||||
TEST_CLASS(UTextAdapterTests);
|
||||
|
||||
TEST_METHOD(Unicode)
|
||||
{
|
||||
DummyRenderer renderer;
|
||||
TextBuffer buffer{ til::size{ 24, 1 }, TextAttribute{}, 0, false, renderer };
|
||||
|
||||
RowWriteState state{
|
||||
.text = L"abc 𝒶𝒷𝒸 abc ネコちゃん",
|
||||
};
|
||||
buffer.Write(0, TextAttribute{}, state);
|
||||
VERIFY_IS_TRUE(state.text.empty());
|
||||
|
||||
static constexpr auto s = [](til::CoordType beg, til::CoordType end) -> til::point_span {
|
||||
return { { beg, 0 }, { end, 0 } };
|
||||
};
|
||||
|
||||
auto expected = std::vector{ s(0, 2), s(8, 10) };
|
||||
auto actual = buffer.SearchText(L"abc", false);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = std::vector{ s(5, 5) };
|
||||
actual = buffer.SearchText(L"𝒷", false);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = std::vector{ s(12, 15) };
|
||||
actual = buffer.SearchText(L"ネコ", false);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
};
|
||||
@@ -17,6 +17,7 @@ SOURCES = \
|
||||
ReflowTests.cpp \
|
||||
TextColorTests.cpp \
|
||||
TextAttributeTests.cpp \
|
||||
UTextAdapterTests.cpp \
|
||||
DefaultResource.rc \
|
||||
|
||||
TARGETLIBS = \
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
<TargetDeviceFamily Name="Windows.DesktopServer" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
<TargetDeviceFamily Name="Windows.DesktopServer" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
<TargetDeviceFamily Name="Windows.DesktopServer" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
||||
@@ -1,404 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalSettingsModel/ColorScheme.h"
|
||||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "../types/inc/colorTable.hpp"
|
||||
#include "JsonTestClass.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 ColorSchemeTests : 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(ColorSchemeTests)
|
||||
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(ParseSimpleColorScheme);
|
||||
TEST_METHOD(LayerColorSchemesOnArray);
|
||||
TEST_METHOD(UpdateSchemeReferences);
|
||||
|
||||
static Core::Color rgb(uint8_t r, uint8_t g, uint8_t b) noexcept
|
||||
{
|
||||
return Core::Color{ r, g, b, 255 };
|
||||
}
|
||||
};
|
||||
|
||||
void ColorSchemeTests::ParseSimpleColorScheme()
|
||||
{
|
||||
const std::string campbellScheme{ "{"
|
||||
"\"background\" : \"#0C0C0C\","
|
||||
"\"black\" : \"#0C0C0C\","
|
||||
"\"blue\" : \"#0037DA\","
|
||||
"\"brightBlack\" : \"#767676\","
|
||||
"\"brightBlue\" : \"#3B78FF\","
|
||||
"\"brightCyan\" : \"#61D6D6\","
|
||||
"\"brightGreen\" : \"#16C60C\","
|
||||
"\"brightPurple\" : \"#B4009E\","
|
||||
"\"brightRed\" : \"#E74856\","
|
||||
"\"brightWhite\" : \"#F2F2F2\","
|
||||
"\"brightYellow\" : \"#F9F1A5\","
|
||||
"\"cursorColor\" : \"#FFFFFF\","
|
||||
"\"cyan\" : \"#3A96DD\","
|
||||
"\"foreground\" : \"#F2F2F2\","
|
||||
"\"green\" : \"#13A10E\","
|
||||
"\"name\" : \"Campbell\","
|
||||
"\"purple\" : \"#881798\","
|
||||
"\"red\" : \"#C50F1F\","
|
||||
"\"selectionBackground\" : \"#131313\","
|
||||
"\"white\" : \"#CCCCCC\","
|
||||
"\"yellow\" : \"#C19C00\""
|
||||
"}" };
|
||||
|
||||
const auto schemeObject = VerifyParseSucceeded(campbellScheme);
|
||||
auto scheme = ColorScheme::FromJson(schemeObject);
|
||||
VERIFY_ARE_EQUAL(L"Campbell", scheme->Name());
|
||||
VERIFY_ARE_EQUAL(til::color(0xf2, 0xf2, 0xf2, 255), til::color{ scheme->Foreground() });
|
||||
VERIFY_ARE_EQUAL(til::color(0x0c, 0x0c, 0x0c, 255), til::color{ scheme->Background() });
|
||||
VERIFY_ARE_EQUAL(til::color(0x13, 0x13, 0x13, 255), til::color{ scheme->SelectionBackground() });
|
||||
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 };
|
||||
Utils::InitializeColorTable(campbellSpan);
|
||||
|
||||
for (size_t i = 0; i < expectedCampbellTable.size(); i++)
|
||||
{
|
||||
const til::color expected{ expectedCampbellTable.at(i) };
|
||||
const til::color actual{ scheme->Table().at(static_cast<uint32_t>(i)) };
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"Roundtrip Test for Color Scheme");
|
||||
auto outJson{ scheme->ToJson() };
|
||||
VERIFY_ARE_EQUAL(schemeObject, outJson);
|
||||
}
|
||||
|
||||
void ColorSchemeTests::LayerColorSchemesOnArray()
|
||||
{
|
||||
static constexpr std::string_view inboxSettings{ R"({
|
||||
"schemes": [
|
||||
{
|
||||
"background": "#0C0C0C",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
static constexpr std::string_view userSettings{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
{
|
||||
"background": "#121314",
|
||||
"black": "#121314",
|
||||
"blue": "#121314",
|
||||
"brightBlack": "#121314",
|
||||
"brightBlue": "#121314",
|
||||
"brightCyan": "#121314",
|
||||
"brightGreen": "#121314",
|
||||
"brightPurple": "#121314",
|
||||
"brightRed": "#121314",
|
||||
"brightWhite": "#121314",
|
||||
"brightYellow": "#121314",
|
||||
"cursorColor": "#121314",
|
||||
"cyan": "#121314",
|
||||
"foreground": "#121314",
|
||||
"green": "#121314",
|
||||
"name": "Campbell",
|
||||
"purple": "#121314",
|
||||
"red": "#121314",
|
||||
"selectionBackground": "#121314",
|
||||
"white": "#121314",
|
||||
"yellow": "#121314"
|
||||
},
|
||||
{
|
||||
"background": "#012456",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell Powershell",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<CascadiaSettings>(userSettings, inboxSettings);
|
||||
|
||||
const auto colorSchemes = settings->GlobalSettings().ColorSchemes();
|
||||
VERIFY_ARE_EQUAL(2u, colorSchemes.Size());
|
||||
|
||||
const auto scheme0 = winrt::get_self<ColorScheme>(colorSchemes.Lookup(L"Campbell"));
|
||||
VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Foreground());
|
||||
VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Background());
|
||||
|
||||
const auto scheme1 = winrt::get_self<ColorScheme>(colorSchemes.Lookup(L"Campbell Powershell"));
|
||||
VERIFY_ARE_EQUAL(rgb(0xCC, 0xCC, 0xCC), scheme1->Foreground());
|
||||
VERIFY_ARE_EQUAL(rgb(0x01, 0x24, 0x56), scheme1->Background());
|
||||
}
|
||||
|
||||
void ColorSchemeTests::UpdateSchemeReferences()
|
||||
{
|
||||
static constexpr std::string_view settingsString{ R"json({
|
||||
"defaultProfile": "Inherited reference",
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"colorScheme": "Campbell"
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"name": "Explicit scheme reference",
|
||||
"colorScheme": "Campbell"
|
||||
},
|
||||
{
|
||||
"name": "Explicit reference; hidden",
|
||||
"colorScheme": "Campbell",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "Inherited reference"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
{
|
||||
"background": "#0C0C0C",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
},
|
||||
{
|
||||
"background": "#0C0C0C",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell (renamed)",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
},
|
||||
{
|
||||
"background": "#282C34",
|
||||
"black": "#282C34",
|
||||
"blue": "#61AFEF",
|
||||
"brightBlack": "#5A6374",
|
||||
"brightBlue": "#61AFEF",
|
||||
"brightCyan": "#56B6C2",
|
||||
"brightGreen": "#98C379",
|
||||
"brightPurple": "#C678DD",
|
||||
"brightRed": "#E06C75",
|
||||
"brightWhite": "#DCDFE4",
|
||||
"brightYellow": "#E5C07B",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#56B6C2",
|
||||
"foreground": "#DCDFE4",
|
||||
"green": "#98C379",
|
||||
"name": "One Half Dark",
|
||||
"purple": "#C678DD",
|
||||
"red": "#E06C75",
|
||||
"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" };
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
{
|
||||
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());
|
||||
}
|
||||
{
|
||||
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());
|
||||
}
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalApp/TerminalPage.h"
|
||||
#include "../LocalTests_SettingsModel/TestUtils.h"
|
||||
#include "../UnitTests_SettingsModel/TestUtils.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace WEX::Logging;
|
||||
|
||||
@@ -133,7 +133,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<TestDll Include="$(OpenConsoleCommonOutDir)\LocalTests_TerminalApp\TerminalApp.LocalTests.dll" />
|
||||
<TestDll Include="$(OpenConsoleCommonOutDir)\LocalTests_SettingsModel\SettingsModel.LocalTests.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="AfterBuild" Inputs="@(TestDll)" Outputs="@(TestDll->'$(TargetDir)'\%(Filename)%(Extension)')">
|
||||
|
||||
@@ -68,6 +68,8 @@ Author(s):
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <SafeDispatcherTimer.h>
|
||||
|
||||
// Common includes for most tests:
|
||||
#include "../../inc/conattrs.hpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
@@ -1065,10 +1065,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
if (termControl.HasSelection())
|
||||
{
|
||||
const auto selections{ termControl.SelectedText(true) };
|
||||
|
||||
// concatenate the selection into a single line
|
||||
auto searchText = std::accumulate(selections.begin(), selections.end(), std::wstring());
|
||||
std::wstring searchText{ termControl.SelectedText(true) };
|
||||
|
||||
// make it compact by replacing consecutive whitespaces with a single space
|
||||
searchText = std::regex_replace(searchText, std::wregex(LR"(\s+)"), L" ");
|
||||
|
||||
@@ -124,8 +124,7 @@ namespace winrt::TerminalApp::implementation
|
||||
return appLogic->GetSettings();
|
||||
}
|
||||
|
||||
AppLogic::AppLogic() :
|
||||
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } }
|
||||
AppLogic::AppLogic()
|
||||
{
|
||||
// For your own sanity, it's better to do setup outside the ctor.
|
||||
// If you do any setup in the ctor that ends up throwing an exception,
|
||||
@@ -327,10 +326,6 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
_reloadSettings->Run();
|
||||
}
|
||||
else if (ApplicationState::SharedInstance().IsStatePath(modifiedBasename))
|
||||
{
|
||||
_reloadState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@ namespace winrt::TerminalApp::implementation
|
||||
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
|
||||
|
||||
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
|
||||
til::throttled_func_trailing<> _reloadState;
|
||||
|
||||
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings{};
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "pch.h"
|
||||
#include "ColorHelper.h"
|
||||
#include <limits>
|
||||
|
||||
using namespace winrt::TerminalApp;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.UI.h>
|
||||
|
||||
namespace winrt::TerminalApp
|
||||
{
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "TabBase.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
import "HighlightedTextControl.idl";
|
||||
import "FilteredCommand.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, Microsoft.Terminal.UI.IDirectKeyListener
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
AllowFocusOnInteraction="True"
|
||||
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
|
||||
@@ -23,12 +23,6 @@
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<!-- This creates an instance of our CommandKeyChordVisibilityConverter we can reference below -->
|
||||
<local:EmptyStringVisibilityConverter x:Key="CommandKeyChordVisibilityConverter" />
|
||||
<local:EmptyStringVisibilityConverter x:Key="ParsedCommandLineTextVisibilityConverter" />
|
||||
<local:EmptyStringVisibilityConverter x:Key="ParentCommandVisibilityConverter" />
|
||||
<model:IconPathConverter x:Key="IconSourceConverter" />
|
||||
|
||||
<DataTemplate x:Key="ListItemTemplate"
|
||||
x:DataType="local:FilteredCommand">
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
@@ -62,8 +56,7 @@
|
||||
|
||||
<!--
|
||||
The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details.
|
||||
when there's actual text set as the label.
|
||||
We're setting the accessibility view on the
|
||||
border and text block to Raw because otherwise,
|
||||
Narrator will read out the key chord. Problem is,
|
||||
@@ -77,7 +70,7 @@
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{ThemeResource FlyoutPresenterBackground}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Visibility="{x:Bind Item.KeyChordText, Mode=OneWay, Converter={StaticResource CommandKeyChordVisibilityConverter}}">
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Item.KeyChordText), Mode=OneWay}">
|
||||
|
||||
<TextBlock AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="12"
|
||||
@@ -113,8 +106,7 @@
|
||||
|
||||
<!--
|
||||
The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details.
|
||||
when there's actual text set as the label.
|
||||
We're setting the accessibility view on the
|
||||
border and text block to Raw because otherwise,
|
||||
Narrator will read out the key chord. Problem is,
|
||||
@@ -127,7 +119,7 @@
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Visibility="{x:Bind Item.KeyChordText, Mode=OneWay, Converter={StaticResource CommandKeyChordVisibilityConverter}}">
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Item.KeyChordText), Mode=OneWay}">
|
||||
|
||||
<TextBlock AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="12"
|
||||
@@ -347,12 +339,12 @@
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{x:Bind PrefixCharacter, Mode=OneWay}"
|
||||
Visibility="{x:Bind PrefixCharacter, Mode=OneWay, Converter={StaticResource ParentCommandVisibilityConverter}}" />
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(PrefixCharacter), Mode=OneWay}" />
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
Margin="8,0,8,8"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{x:Bind ParentCommandName, Mode=OneWay, Converter={StaticResource ParentCommandVisibilityConverter}}">
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(ParentCommandName), Mode=OneWay}">
|
||||
|
||||
<Button x:Name="_parentCommandBackButton"
|
||||
x:Uid="ParentCommandBackButton"
|
||||
@@ -377,7 +369,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Style="{ThemeResource ParsedCommandLineBorderStyle}"
|
||||
Visibility="{x:Bind ParsedCommandLineText, Mode=OneWay, Converter={StaticResource ParsedCommandLineTextVisibilityConverter}}">
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(ParsedCommandLineText), Mode=OneWay}">
|
||||
|
||||
<ScrollViewer MaxHeight="200"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
|
||||
void TerminalOutput(const winrt::event_token& token) noexcept { _wrappedConnection.TerminalOutput(token); };
|
||||
winrt::event_token StateChanged(const TypedEventHandler<ITerminalConnection, IInspectable>& handler) { return _wrappedConnection.StateChanged(handler); };
|
||||
void StateChanged(const winrt::event_token& token) noexcept { _wrappedConnection.StateChanged(token); };
|
||||
winrt::guid SessionId() const noexcept { return {}; }
|
||||
ConnectionState State() const noexcept { return _wrappedConnection.State(); }
|
||||
|
||||
private:
|
||||
@@ -98,6 +99,15 @@ namespace winrt::Microsoft::TerminalApp::implementation
|
||||
_wrappedConnection = nullptr;
|
||||
}
|
||||
|
||||
guid DebugTapConnection::SessionId() const noexcept
|
||||
{
|
||||
if (const auto c = _wrappedConnection.get())
|
||||
{
|
||||
return c.SessionId();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ConnectionState DebugTapConnection::State() const noexcept
|
||||
{
|
||||
if (auto strongConnection{ _wrappedConnection.get() })
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace winrt::Microsoft::TerminalApp::implementation
|
||||
void WriteInput(const hstring& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
|
||||
winrt::guid SessionId() const noexcept;
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept;
|
||||
|
||||
void SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "EmptyStringVisibilityConverter.h"
|
||||
#include "EmptyStringVisibilityConverter.g.cpp"
|
||||
|
||||
using namespace winrt::Windows;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Method Description:
|
||||
// - Attempt to convert something into another type. For the
|
||||
// EmptyStringVisibilityConverter, we're gonna check if `value` is a
|
||||
// string, and try and convert it into a Visibility value. If the input
|
||||
// param wasn't a string, or was the empty string, we'll return
|
||||
// Visibility::Collapsed. Otherwise, we'll return Visible.
|
||||
|
||||
// Arguments:
|
||||
// - value: the input object to attempt to convert into a Visibility.
|
||||
// Return Value:
|
||||
// - Visible if the object was a string and wasn't the empty string.
|
||||
Foundation::IInspectable EmptyStringVisibilityConverter::Convert(const Foundation::IInspectable& value,
|
||||
const Windows::UI::Xaml::Interop::TypeName& /* targetType */,
|
||||
const Foundation::IInspectable& /* parameter */,
|
||||
const hstring& /* language */)
|
||||
{
|
||||
const auto& name = winrt::unbox_value_or<hstring>(value, L"");
|
||||
return winrt::box_value(name.empty() ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
// unused for one-way bindings
|
||||
Foundation::IInspectable EmptyStringVisibilityConverter::ConvertBack(const Foundation::IInspectable& /* value */,
|
||||
const Windows::UI::Xaml::Interop::TypeName& /* targetType */,
|
||||
const Foundation::IInspectable& /* parameter */,
|
||||
const hstring& /* language */)
|
||||
{
|
||||
throw hresult_not_implemented();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "EmptyStringVisibilityConverter.g.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct EmptyStringVisibilityConverter : EmptyStringVisibilityConverterT<EmptyStringVisibilityConverter>
|
||||
{
|
||||
EmptyStringVisibilityConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(const Windows::Foundation::IInspectable& value,
|
||||
const Windows::UI::Xaml::Interop::TypeName& targetType,
|
||||
const Windows::Foundation::IInspectable& parameter,
|
||||
const hstring& language);
|
||||
|
||||
Windows::Foundation::IInspectable ConvertBack(const Windows::Foundation::IInspectable& value,
|
||||
const Windows::UI::Xaml::Interop::TypeName& targetType,
|
||||
const Windows::Foundation::IInspectable& parameter,
|
||||
const hstring& language);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(EmptyStringVisibilityConverter);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
// See https://docs.microsoft.com/en-us/windows/uwp/data-binding/data-binding-quickstart
|
||||
|
||||
// We use the default attribute to declare IValueConverter as the default
|
||||
// interface. In the listing, EmptyStringVisibilityConverter has only a
|
||||
// constructor, and no methods, so no default interface is generated for it.
|
||||
// The default attribute is optimal if you won't be adding instance members
|
||||
// to EmptyStringVisibilityConverter, because no QueryInterface will be
|
||||
// required to call the IValueConverter methods
|
||||
runtimeclass EmptyStringVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
EmptyStringVisibilityConverter();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
// C++/winrt makes it difficult to share this idl between two projects,
|
||||
// Instead, we just pin the uuid and include it in both TermControl and App
|
||||
// If you update this one, please update the one in TerminalControl\TermControl.idl
|
||||
// If you change this interface, please update the guid.
|
||||
// If you press F7 or Alt and get a runtime error, go make sure both copies are the same.
|
||||
[uuid("0ddf4edc-3fda-4dee-97ca-a417ee3dd510")] interface IDirectKeyListener {
|
||||
Boolean OnDirectKeyEvent(UInt32 vkey, UInt8 scanCode, Boolean down);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
Controls::IconElement PaletteItem::ResolvedIcon()
|
||||
{
|
||||
const auto icon = IconPathConverter::IconWUX(Icon());
|
||||
const auto icon = Microsoft::Terminal::UI::IconPathConverter::IconWUX(Icon());
|
||||
icon.Width(16);
|
||||
icon.Height(16);
|
||||
return icon;
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// The TabViewItem Icon needs MUX while the IconSourceElement in the CommandPalette needs WUX...
|
||||
Icon(winrt::hstring{ glyph });
|
||||
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(glyph, false));
|
||||
TabViewItem().IconSource(Microsoft::Terminal::UI::IconPathConverter::IconSourceMUX(glyph, false));
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Media::Brush SettingsTab::_BackgroundBrush()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "TabBase.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
import "HighlightedTextControl.idl";
|
||||
import "FilteredCommand.idl";
|
||||
|
||||
@@ -21,7 +20,7 @@ namespace TerminalApp
|
||||
BottomUp
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass SuggestionsControl : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener
|
||||
[default_interface] runtimeclass SuggestionsControl : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, Microsoft.Terminal.UI.IDirectKeyListener
|
||||
{
|
||||
SuggestionsControl();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
AllowFocusOnInteraction="True"
|
||||
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
|
||||
@@ -23,9 +24,6 @@
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<local:EmptyStringVisibilityConverter x:Key="ParentCommandVisibilityConverter" />
|
||||
<model:IconPathConverter x:Key="IconSourceConverter" />
|
||||
|
||||
<DataTemplate x:Key="ListItemTemplate"
|
||||
x:DataType="local:FilteredCommand">
|
||||
<ListViewItem Height="32"
|
||||
@@ -161,7 +159,7 @@
|
||||
<StackPanel Grid.Row="1"
|
||||
Margin="8,0,8,8"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{x:Bind ParentCommandName, Mode=OneWay, Converter={StaticResource ParentCommandVisibilityConverter}}">
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(ParentCommandName), Mode=OneWay}">
|
||||
|
||||
<Button x:Name="_parentCommandBackButton"
|
||||
x:Uid="ParentCommandBackButton"
|
||||
|
||||
@@ -174,11 +174,12 @@ namespace winrt::TerminalApp::implementation
|
||||
// Set this tab's icon to the icon from the user's profile
|
||||
if (const auto profile{ newTabImpl->GetFocusedProfile() })
|
||||
{
|
||||
if (!profile.Icon().empty())
|
||||
const auto& icon = profile.EvaluatedIcon();
|
||||
if (!icon.empty())
|
||||
{
|
||||
const auto theme = _settings.GlobalSettings().CurrentTheme();
|
||||
const auto iconStyle = (theme && theme.Tab()) ? theme.Tab().IconStyle() : IconStyle::Default;
|
||||
newTabImpl->UpdateIcon(profile.Icon(), iconStyle);
|
||||
newTabImpl->UpdateIcon(icon, iconStyle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +246,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
const auto theme = _settings.GlobalSettings().CurrentTheme();
|
||||
const auto iconStyle = (theme && theme.Tab()) ? theme.Tab().IconStyle() : IconStyle::Default;
|
||||
tab.UpdateIcon(profile.Icon(), iconStyle);
|
||||
tab.UpdateIcon(profile.EvaluatedIcon(), iconStyle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,9 +130,6 @@
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FilteredCommand.h" />
|
||||
<ClInclude Include="EmptyStringVisibilityConverter.h">
|
||||
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Pane.h" />
|
||||
<ClInclude Include="ColorHelper.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
@@ -232,12 +229,11 @@
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FilteredCommand.cpp" />
|
||||
<ClCompile Include="EmptyStringVisibilityConverter.cpp">
|
||||
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Pane.cpp" />
|
||||
<ClCompile Include="Pane.LayoutSizeNode.cpp" />
|
||||
<ClCompile Include="ColorHelper.cpp" />
|
||||
<ClCompile Include="ColorHelper.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DebugTapConnection.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@@ -278,7 +274,6 @@
|
||||
header in TerminalApp.vcxproj (as well as in this file) -->
|
||||
<Midl Include="ActionPaletteItem.idl" />
|
||||
<Midl Include="CommandLinePaletteItem.idl" />
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="AboutDialog.idl">
|
||||
<DependentUpon>AboutDialog.xaml</DependentUpon>
|
||||
</Midl>
|
||||
@@ -339,7 +334,6 @@
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="FilteredCommand.idl" />
|
||||
<Midl Include="EmptyStringVisibilityConverter.idl" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
@@ -383,6 +377,9 @@
|
||||
<Private>true</Private>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\UIHelpers\UIHelpers.vcxproj">
|
||||
<Project>{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -98,7 +98,6 @@
|
||||
<Midl Include="ShortcutActionDispatch.idl">
|
||||
<Filter>settings</Filter>
|
||||
</Midl>
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="SettingsTab.idl">
|
||||
<Filter>tab</Filter>
|
||||
</Midl>
|
||||
@@ -116,7 +115,6 @@
|
||||
</Midl>
|
||||
<Midl Include="PaletteItemTemplateSelector.idl" />
|
||||
<Midl Include="TabBase.idl" />
|
||||
<Midl Include="EmptyStringVisibilityConverter.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
@@ -187,4 +185,4 @@
|
||||
<Filter>app</Filter>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1033,9 +1033,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// If there's an icon set for this profile, set it as the icon for
|
||||
// this flyout item
|
||||
if (!profile.Icon().empty())
|
||||
const auto& iconPath = profile.EvaluatedIcon();
|
||||
if (!iconPath.empty())
|
||||
{
|
||||
const auto icon = _CreateNewTabFlyoutIcon(profile.Icon());
|
||||
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
|
||||
profileMenuItem.Icon(icon);
|
||||
}
|
||||
|
||||
@@ -1100,7 +1101,7 @@ namespace winrt::TerminalApp::implementation
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto icon = IconPathConverter::IconWUX(iconSource);
|
||||
auto icon = UI::IconPathConverter::IconWUX(iconSource);
|
||||
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);
|
||||
|
||||
return icon;
|
||||
@@ -1210,7 +1211,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TerminalConnection::ITerminalConnection connection{ nullptr };
|
||||
|
||||
auto connectionType = profile.ConnectionType();
|
||||
winrt::guid sessionGuid{};
|
||||
Windows::Foundation::Collections::ValueSet valueSet;
|
||||
|
||||
if (connectionType == TerminalConnection::AzureConnection::ConnectionType() &&
|
||||
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
|
||||
@@ -1226,23 +1227,16 @@ namespace winrt::TerminalApp::implementation
|
||||
connection = TerminalConnection::ConptyConnection{};
|
||||
}
|
||||
|
||||
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(),
|
||||
L".",
|
||||
L"Azure",
|
||||
false,
|
||||
L"",
|
||||
nullptr,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid(),
|
||||
profile.Guid());
|
||||
|
||||
if constexpr (Feature_VtPassthroughMode::IsEnabled())
|
||||
{
|
||||
valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
|
||||
}
|
||||
|
||||
connection.Initialize(valueSet);
|
||||
valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(),
|
||||
L".",
|
||||
L"Azure",
|
||||
false,
|
||||
L"",
|
||||
nullptr,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid(),
|
||||
profile.Guid());
|
||||
}
|
||||
|
||||
else
|
||||
@@ -1267,38 +1261,38 @@ namespace winrt::TerminalApp::implementation
|
||||
// process until later, on another thread, after we've already
|
||||
// restored the CWD to its original value.
|
||||
auto newWorkingDirectory{ _evaluatePathForCwd(settings.StartingDirectory()) };
|
||||
auto conhostConn = TerminalConnection::ConptyConnection();
|
||||
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
|
||||
newWorkingDirectory,
|
||||
settings.StartingTitle(),
|
||||
settings.ReloadEnvironmentVariables(),
|
||||
_WindowProperties.VirtualEnvVars(),
|
||||
environment,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid(),
|
||||
profile.Guid());
|
||||
|
||||
valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
|
||||
connection = TerminalConnection::ConptyConnection{};
|
||||
valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
|
||||
newWorkingDirectory,
|
||||
settings.StartingTitle(),
|
||||
settings.ReloadEnvironmentVariables(),
|
||||
_WindowProperties.VirtualEnvVars(),
|
||||
environment,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid(),
|
||||
profile.Guid());
|
||||
|
||||
if (inheritCursor)
|
||||
{
|
||||
valueSet.Insert(L"inheritCursor", Windows::Foundation::PropertyValue::CreateBoolean(true));
|
||||
}
|
||||
|
||||
conhostConn.Initialize(valueSet);
|
||||
|
||||
sessionGuid = conhostConn.Guid();
|
||||
connection = conhostConn;
|
||||
}
|
||||
|
||||
if constexpr (Feature_VtPassthroughMode::IsEnabled())
|
||||
{
|
||||
valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
|
||||
}
|
||||
|
||||
connection.Initialize(valueSet);
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"ConnectionCreated",
|
||||
TraceLoggingDescription("Event emitted upon the creation of a connection"),
|
||||
TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"),
|
||||
TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The profile's GUID"),
|
||||
TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingGuid(connection.SessionId(), "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
|
||||
@@ -2593,12 +2587,9 @@ namespace winrt::TerminalApp::implementation
|
||||
auto dataPack = DataPackage();
|
||||
dataPack.RequestedOperation(DataPackageOperation::Copy);
|
||||
|
||||
// The EventArgs.Formats() is an override for the global setting "copyFormatting"
|
||||
// iff it is set
|
||||
auto useGlobal = copiedData.Formats() == nullptr;
|
||||
auto copyFormats = useGlobal ?
|
||||
_settings.GlobalSettings().CopyFormatting() :
|
||||
copiedData.Formats().Value();
|
||||
const auto copyFormats = copiedData.Formats() != nullptr ?
|
||||
copiedData.Formats().Value() :
|
||||
static_cast<CopyFormat>(0);
|
||||
|
||||
// copy text to dataPack
|
||||
dataPack.SetText(copiedData.Text());
|
||||
@@ -2631,6 +2622,75 @@ namespace winrt::TerminalApp::implementation
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
static wil::unique_close_clipboard_call _openClipboard(HWND hwnd)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
// OpenClipboard may fail to acquire the internal lock --> retry.
|
||||
for (DWORD sleep = 10;; sleep *= 2)
|
||||
{
|
||||
if (OpenClipboard(hwnd))
|
||||
{
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
// 10 iterations
|
||||
if (sleep > 10000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Sleep(sleep);
|
||||
}
|
||||
|
||||
return wil::unique_close_clipboard_call{ success };
|
||||
}
|
||||
|
||||
static winrt::hstring _extractClipboard()
|
||||
{
|
||||
// This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically.
|
||||
if (const auto handle = GetClipboardData(CF_UNICODETEXT))
|
||||
{
|
||||
const wil::unique_hglobal_locked lock{ handle };
|
||||
const auto str = static_cast<const wchar_t*>(lock.get());
|
||||
if (!str)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto maxLen = GlobalSize(handle) / sizeof(wchar_t);
|
||||
const auto len = wcsnlen(str, maxLen);
|
||||
return winrt::hstring{ str, gsl::narrow_cast<uint32_t>(len) };
|
||||
}
|
||||
|
||||
// We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others).
|
||||
if (const auto handle = GetClipboardData(CF_HDROP))
|
||||
{
|
||||
const wil::unique_hglobal_locked lock{ handle };
|
||||
const auto drop = static_cast<HDROP>(lock.get());
|
||||
if (!drop)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto cap = DragQueryFileW(drop, 0, nullptr, 0);
|
||||
if (cap == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto buffer = winrt::impl::hstring_builder{ cap };
|
||||
const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1);
|
||||
if (len == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return buffer.to_hstring();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - This function is called when the `TermControl` requests that we send
|
||||
// it the clipboard's content.
|
||||
@@ -2650,53 +2710,14 @@ namespace winrt::TerminalApp::implementation
|
||||
const auto weakThis = get_weak();
|
||||
const auto dispatcher = Dispatcher();
|
||||
const auto globalSettings = _settings.GlobalSettings();
|
||||
winrt::hstring text;
|
||||
|
||||
// GetClipboardData might block for up to 30s for delay-rendered contents.
|
||||
co_await winrt::resume_background();
|
||||
|
||||
winrt::hstring text;
|
||||
if (const auto clipboard = _openClipboard(nullptr))
|
||||
{
|
||||
// According to various reports on the internet, OpenClipboard might
|
||||
// fail to acquire the internal lock, for instance due to rdpclip.exe.
|
||||
for (int attempts = 1;;)
|
||||
{
|
||||
if (OpenClipboard(nullptr))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (attempts > 5)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
Sleep(10 * attempts);
|
||||
}
|
||||
|
||||
const auto clipboardCleanup = wil::scope_exit([]() {
|
||||
CloseClipboard();
|
||||
});
|
||||
|
||||
const auto data = GetClipboardData(CF_UNICODETEXT);
|
||||
if (!data)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
const auto str = static_cast<const wchar_t*>(GlobalLock(data));
|
||||
if (!str)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
const auto dataCleanup = wil::scope_exit([&]() {
|
||||
GlobalUnlock(data);
|
||||
});
|
||||
|
||||
const auto maxLength = GlobalSize(data) / sizeof(wchar_t);
|
||||
const auto length = wcsnlen(str, maxLength);
|
||||
text = winrt::hstring{ str, gsl::narrow_cast<uint32_t>(length) };
|
||||
text = _extractClipboard();
|
||||
}
|
||||
|
||||
if (globalSettings.TrimPaste())
|
||||
@@ -4904,7 +4925,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (!icon.empty())
|
||||
{
|
||||
auto iconElement = IconPathConverter::IconWUX(icon);
|
||||
auto iconElement = UI::IconPathConverter::IconWUX(icon);
|
||||
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
|
||||
button.Icon(iconElement);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
import "TaskbarState.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
@@ -57,7 +56,7 @@ namespace TerminalApp
|
||||
Boolean IsQuakeWindow();
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener
|
||||
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, Microsoft.Terminal.UI.IDirectKeyListener
|
||||
{
|
||||
TerminalPage(WindowProperties properties, ContentManager manager);
|
||||
|
||||
|
||||
@@ -121,12 +121,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalTab::_BellIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/)
|
||||
{
|
||||
ShowBellIndicator(false);
|
||||
// Just do a sanity check that the timer still exists before we stop it
|
||||
if (_bellIndicatorTimer.has_value())
|
||||
{
|
||||
_bellIndicatorTimer->Stop();
|
||||
_bellIndicatorTimer = std::nullopt;
|
||||
}
|
||||
_bellIndicatorTimer.Stop();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -309,7 +304,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
Icon(_lastIconPath);
|
||||
bool isMonochrome = iconStyle == IconStyle::Monochrome;
|
||||
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath, isMonochrome));
|
||||
TabViewItem().IconSource(Microsoft::Terminal::UI::IconPathConverter::IconSourceMUX(_lastIconPath, isMonochrome));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +327,7 @@ namespace winrt::TerminalApp::implementation
|
||||
else
|
||||
{
|
||||
Icon(_lastIconPath);
|
||||
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath, _lastIconStyle == IconStyle::Monochrome));
|
||||
TabViewItem().IconSource(Microsoft::Terminal::UI::IconPathConverter::IconSourceMUX(_lastIconPath, _lastIconStyle == IconStyle::Monochrome));
|
||||
}
|
||||
_iconHidden = hide;
|
||||
}
|
||||
@@ -356,14 +351,13 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
ASSERT_UI_THREAD();
|
||||
|
||||
if (!_bellIndicatorTimer.has_value())
|
||||
if (!_bellIndicatorTimer)
|
||||
{
|
||||
DispatcherTimer bellIndicatorTimer;
|
||||
bellIndicatorTimer.Interval(std::chrono::milliseconds(2000));
|
||||
bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick });
|
||||
bellIndicatorTimer.Start();
|
||||
_bellIndicatorTimer.emplace(std::move(bellIndicatorTimer));
|
||||
_bellIndicatorTimer.Interval(std::chrono::milliseconds(2000));
|
||||
_bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick });
|
||||
}
|
||||
|
||||
_bellIndicatorTimer.Start();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _Setup();
|
||||
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer;
|
||||
SafeDispatcherTimer _bellIndicatorTimer;
|
||||
void _BellIndicatorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e);
|
||||
|
||||
void _MakeTabViewItem() override;
|
||||
|
||||
@@ -863,7 +863,7 @@ namespace winrt::TerminalApp::implementation
|
||||
auto focusedObject{ Windows::UI::Xaml::Input::FocusManager::GetFocusedElement(xamlRoot) };
|
||||
do
|
||||
{
|
||||
if (auto keyListener{ focusedObject.try_as<IDirectKeyListener>() })
|
||||
if (auto keyListener{ focusedObject.try_as<UI::IDirectKeyListener>() })
|
||||
{
|
||||
if (keyListener.OnDirectKeyEvent(vkey, scanCode, down))
|
||||
{
|
||||
@@ -891,7 +891,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// don't want to go around the loop again.
|
||||
if (!focusedObject)
|
||||
{
|
||||
if (auto keyListener{ _root.try_as<IDirectKeyListener>() })
|
||||
if (auto keyListener{ _root.try_as<UI::IDirectKeyListener>() })
|
||||
{
|
||||
return keyListener.OnDirectKeyEvent(vkey, scanCode, down);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import "TerminalPage.idl";
|
||||
import "ShortcutActionDispatch.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
@@ -40,7 +39,7 @@ namespace TerminalApp
|
||||
|
||||
// See IDialogPresenter and TerminalPage's DialogPresenter for more
|
||||
// information.
|
||||
[default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
[default_interface] runtimeclass TerminalWindow : Microsoft.Terminal.UI.IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
TerminalWindow(SettingsLoadEventArgs result, ContentManager manager);
|
||||
|
||||
|
||||
@@ -34,5 +34,5 @@ public:
|
||||
|
||||
private:
|
||||
winrt::Microsoft::UI::Xaml::Controls::TeachingTip _tip;
|
||||
winrt::Windows::UI::Xaml::DispatcherTimer _timer;
|
||||
SafeDispatcherTimer _timer;
|
||||
};
|
||||
|
||||
@@ -70,6 +70,9 @@
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\UIHelpers\UIHelpers.vcxproj">
|
||||
<Project>{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}</Project>
|
||||
</ProjectReference>
|
||||
<!-- Reference TerminalAppLib here, so we can use its TerminalApp.winmd as
|
||||
our TerminalApp.winmd. This didn't work correctly in VS2017, you'd need to
|
||||
manually reference the lib -->
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
|
||||
#include <winrt/Microsoft.Terminal.Settings.Editor.h>
|
||||
#include <winrt/Microsoft.Terminal.Settings.Model.h>
|
||||
#include <winrt/Microsoft.Terminal.UI.h>
|
||||
#include <winrt/Windows.Services.Store.h>
|
||||
#include <winrt/Windows.Storage.h>
|
||||
#include <winrt/Windows.Storage.Provider.h>
|
||||
@@ -83,6 +84,8 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <SafeDispatcherTimer.h>
|
||||
|
||||
#include <cppwinrt_utils.h>
|
||||
#include <wil/cppwinrt_helpers.h> // must go after the CoreDispatcher type is defined
|
||||
|
||||
|
||||
@@ -77,8 +77,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
if (settings)
|
||||
{
|
||||
_initialRows = gsl::narrow<til::CoordType>(winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows));
|
||||
_initialCols = gsl::narrow<til::CoordType>(winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols));
|
||||
_initialRows = unbox_prop_or<uint32_t>(settings, L"initialRows", _initialRows);
|
||||
_initialCols = unbox_prop_or<uint32_t>(settings, L"initialCols", _initialCols);
|
||||
_sessionId = unbox_prop_or<guid>(settings, L"sessionId", _sessionId);
|
||||
}
|
||||
|
||||
if (_sessionId == guid{})
|
||||
{
|
||||
_sessionId = Utils::CreateGuid();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "ConnectionStateHolder.h"
|
||||
#include "BaseTerminalConnection.h"
|
||||
#include "AzureClient.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct AzureConnection : AzureConnectionT<AzureConnection>, ConnectionStateHolder<AzureConnection>
|
||||
struct AzureConnection : AzureConnectionT<AzureConnection>, BaseTerminalConnection<AzureConnection>
|
||||
{
|
||||
static winrt::guid ConnectionType() noexcept;
|
||||
static bool IsAzureConnectionAvailable() noexcept;
|
||||
|
||||
@@ -4,13 +4,28 @@
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
template<typename T>
|
||||
struct ConnectionStateHolder
|
||||
struct BaseTerminalConnection
|
||||
{
|
||||
public:
|
||||
ConnectionState State() const noexcept { return _connectionState; }
|
||||
winrt::guid SessionId() const noexcept
|
||||
{
|
||||
return _sessionId;
|
||||
}
|
||||
|
||||
ConnectionState State() const noexcept
|
||||
{
|
||||
return _connectionState;
|
||||
}
|
||||
|
||||
TYPED_EVENT(StateChanged, ITerminalConnection, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
protected:
|
||||
template<typename U>
|
||||
U unbox_prop_or(const Windows::Foundation::Collections::ValueSet& blob, std::wstring_view key, U defaultValue)
|
||||
{
|
||||
return winrt::unbox_value_or<U>(blob.TryLookup(key).try_as<Windows::Foundation::IPropertyValue>(), defaultValue);
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26447) // Analyzer is still upset about noexcepts throwing even with function level try.
|
||||
// Method Description:
|
||||
@@ -86,6 +101,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return _isStateOneOf(ConnectionState::Connected);
|
||||
}
|
||||
|
||||
winrt::guid _sessionId{};
|
||||
|
||||
private:
|
||||
std::atomic<ConnectionState> _connectionState{ ConnectionState::NotConnected };
|
||||
mutable std::mutex _stateMutex;
|
||||
@@ -85,18 +85,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
auto environment = _initialEnv;
|
||||
|
||||
{
|
||||
// Convert connection Guid to string and ignore the enclosing '{}'.
|
||||
auto wsGuid{ Utils::GuidToString(_guid) };
|
||||
wsGuid.pop_back();
|
||||
|
||||
const auto guidSubStr = std::wstring_view{ wsGuid }.substr(1);
|
||||
|
||||
// Ensure every connection has the unique identifier in the environment.
|
||||
environment.as_map().insert_or_assign(L"WT_SESSION", guidSubStr.data());
|
||||
// Convert connection Guid to string and ignore the enclosing '{}'.
|
||||
environment.as_map().insert_or_assign(L"WT_SESSION", Utils::GuidToPlainString(_sessionId));
|
||||
|
||||
// The profile Guid does include the enclosing '{}'
|
||||
const auto profileGuid{ Utils::GuidToString(_profileGuid) };
|
||||
environment.as_map().insert_or_assign(L"WT_PROFILE_ID", profileGuid.data());
|
||||
environment.as_map().insert_or_assign(L"WT_PROFILE_ID", Utils::GuidToString(_profileGuid));
|
||||
|
||||
// WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL
|
||||
// https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
|
||||
@@ -104,7 +98,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
if (_environment)
|
||||
{
|
||||
// Order the environment variable names so that resolution order is consistent
|
||||
std::set<std::wstring, til::wstring_case_insensitive_compare> keys{};
|
||||
std::set<std::wstring, til::env_key_sorter> keys{};
|
||||
for (const auto item : _environment)
|
||||
{
|
||||
keys.insert(item.Key().c_str());
|
||||
@@ -171,7 +165,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
g_hTerminalConnectionProvider,
|
||||
"ConPtyConnected",
|
||||
TraceLoggingDescription("Event emitted when ConPTY connection is started"),
|
||||
TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
@@ -189,7 +183,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
TERMINAL_STARTUP_INFO startupInfo) :
|
||||
_rows{ 25 },
|
||||
_cols{ 80 },
|
||||
_guid{ Utils::CreateGuid() },
|
||||
_inPipe{ hIn },
|
||||
_outPipe{ hOut }
|
||||
{
|
||||
@@ -249,12 +242,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return vs;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T unbox_prop_or(const Windows::Foundation::Collections::ValueSet& blob, std::wstring_view key, T defaultValue)
|
||||
{
|
||||
return winrt::unbox_value_or<T>(blob.TryLookup(key).try_as<Windows::Foundation::IPropertyValue>(), defaultValue);
|
||||
}
|
||||
|
||||
void ConptyConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
|
||||
{
|
||||
if (settings)
|
||||
@@ -268,7 +255,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_startingTitle = unbox_prop_or<winrt::hstring>(settings, L"startingTitle", _startingTitle);
|
||||
_rows = unbox_prop_or<uint32_t>(settings, L"initialRows", _rows);
|
||||
_cols = unbox_prop_or<uint32_t>(settings, L"initialCols", _cols);
|
||||
_guid = unbox_prop_or<winrt::guid>(settings, L"guid", _guid);
|
||||
_sessionId = unbox_prop_or<winrt::guid>(settings, L"sessionId", _sessionId);
|
||||
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
|
||||
if constexpr (Feature_VtPassthroughMode::IsEnabled())
|
||||
{
|
||||
@@ -299,17 +286,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_initialEnv = til::env::from_current_environment();
|
||||
}
|
||||
}
|
||||
|
||||
if (_guid == guid{})
|
||||
{
|
||||
_guid = Utils::CreateGuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
winrt::guid ConptyConnection::Guid() const noexcept
|
||||
{
|
||||
return _guid;
|
||||
if (_sessionId == guid{})
|
||||
{
|
||||
_sessionId = Utils::CreateGuid();
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring ConptyConnection::Commandline() const
|
||||
@@ -382,7 +364,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
g_hTerminalConnectionProvider,
|
||||
"ConPtyConnectedToDefterm",
|
||||
TraceLoggingDescription("Event emitted when ConPTY connection is started, for a defterm session"),
|
||||
TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
@@ -686,7 +668,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
TraceLoggingWrite(g_hTerminalConnectionProvider,
|
||||
"ReceivedFirstByte",
|
||||
TraceLoggingDescription("An event emitted when the connection receives the first byte"),
|
||||
TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingFloat64(delta.count(), "Duration"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "ConptyConnection.g.h"
|
||||
#include "ConnectionStateHolder.h"
|
||||
#include "BaseTerminalConnection.h"
|
||||
|
||||
#include "ITerminalHandoff.h"
|
||||
#include <til/env.h>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, ConnectionStateHolder<ConptyConnection>
|
||||
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, BaseTerminalConnection<ConptyConnection>
|
||||
{
|
||||
ConptyConnection(const HANDLE hSig,
|
||||
const HANDLE hIn,
|
||||
@@ -36,7 +36,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
void ReparentWindow(const uint64_t newParent);
|
||||
|
||||
winrt::guid Guid() const noexcept;
|
||||
winrt::hstring Commandline() const;
|
||||
winrt::hstring StartingTitle() const;
|
||||
WORD ShowWindow() const noexcept;
|
||||
@@ -77,7 +76,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
hstring _startingTitle{};
|
||||
bool _initialVisibility{ true };
|
||||
Windows::Foundation::Collections::ValueSet _environment{ nullptr };
|
||||
guid _guid{}; // A unique session identifier for connected client
|
||||
hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch).
|
||||
|
||||
bool _receivedFirstByte{ false };
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace Microsoft.Terminal.TerminalConnection
|
||||
[default_interface] runtimeclass ConptyConnection : ITerminalConnection
|
||||
{
|
||||
ConptyConnection();
|
||||
Guid Guid { get; };
|
||||
String Commandline { get; };
|
||||
String StartingTitle { get; };
|
||||
UInt16 ShowWindow { get; };
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) const noexcept {};
|
||||
|
||||
winrt::guid SessionId() const noexcept { return {}; }
|
||||
ConnectionState State() const noexcept { return ConnectionState::Connected; }
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
@@ -25,8 +25,9 @@ namespace Microsoft.Terminal.TerminalConnection
|
||||
void Close();
|
||||
|
||||
event TerminalOutputHandler TerminalOutput;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<ITerminalConnection, Object> StateChanged;
|
||||
|
||||
Guid SessionId { get; };
|
||||
ConnectionState State { get; };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AzureClientID.h" />
|
||||
<ClInclude Include="BaseTerminalConnection.h" />
|
||||
<ClInclude Include="ConnectionInformation.h">
|
||||
<DependentUpon>ConnectionInformation.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<ClInclude Include="AzureConnection.h" />
|
||||
<ClInclude Include="AzureClientID.h" />
|
||||
<ClInclude Include="CTerminalHandoff.h" />
|
||||
<ClInclude Include="BaseTerminalConnection.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="ITerminalConnection.idl" />
|
||||
@@ -34,11 +35,9 @@
|
||||
<Midl Include="ConptyConnection.idl" />
|
||||
<Midl Include="ConnectionInformation.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "EventArgs.h"
|
||||
#include "../../buffer/out/search.h"
|
||||
#include "../../renderer/atlas/AtlasEngine.h"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
|
||||
#include "ControlCore.g.cpp"
|
||||
#include "SelectionColor.g.cpp"
|
||||
@@ -335,15 +334,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_settings->UseAtlasEngine())
|
||||
{
|
||||
_renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
}
|
||||
|
||||
_renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
_renderer->AddRenderEngine(_renderEngine.get());
|
||||
|
||||
// Initialize our font with the renderer
|
||||
@@ -359,7 +350,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
|
||||
LOG_IF_FAILED(_renderEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
|
||||
// Update DxEngine's SelectionBackground
|
||||
// Update AtlasEngine's SelectionBackground
|
||||
_renderEngine->SetSelectionBackground(til::color{ _settings->SelectionBackground() });
|
||||
|
||||
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
|
||||
@@ -869,6 +860,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
const auto lock = _terminal->LockForWriting();
|
||||
|
||||
_builtinGlyphs = _settings->EnableBuiltinGlyphs();
|
||||
_cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str());
|
||||
_cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str());
|
||||
_runtimeOpacity = std::nullopt;
|
||||
@@ -915,10 +907,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Update the terminal core with its new Core settings
|
||||
_terminal->UpdateAppearance(*newAppearance);
|
||||
|
||||
// Update DxEngine settings under the lock
|
||||
// Update AtlasEngine settings under the lock
|
||||
if (_renderEngine)
|
||||
{
|
||||
// Update DxEngine settings under the lock
|
||||
// Update AtlasEngine settings under the lock
|
||||
_renderEngine->SetSelectionBackground(til::color{ newAppearance->SelectionBackground() });
|
||||
_renderEngine->SetRetroTerminalEffect(newAppearance->RetroTerminalEffect());
|
||||
_renderEngine->SetPixelShaderPath(newAppearance->PixelShaderPath());
|
||||
@@ -954,7 +946,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void ControlCore::_updateAntiAliasingMode()
|
||||
{
|
||||
D2D1_TEXT_ANTIALIAS_MODE mode;
|
||||
// Update DxEngine's AntialiasingMode
|
||||
// Update AtlasEngine's AntialiasingMode
|
||||
switch (_settings->AntialiasingMode())
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
@@ -1047,6 +1039,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false };
|
||||
_actualFontFaceName = { fontFace };
|
||||
|
||||
_desiredFont.SetEnableBuiltinGlyphs(_builtinGlyphs);
|
||||
_desiredFont.SetCellSize(_cellWidth, _cellHeight);
|
||||
|
||||
const auto before = _actualFont.GetSize();
|
||||
@@ -1250,44 +1243,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
// use action's copyFormatting if it's present, else fallback to globally
|
||||
// set copyFormatting.
|
||||
const auto copyFormats = formats != nullptr ? formats.Value() : _settings->CopyFormatting();
|
||||
|
||||
const auto copyHtml = WI_IsFlagSet(copyFormats, CopyFormat::HTML);
|
||||
const auto copyRtf = WI_IsFlagSet(copyFormats, CopyFormat::RTF);
|
||||
|
||||
// extract text from buffer
|
||||
// RetrieveSelectedTextFromBuffer will lock while it's reading
|
||||
const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(singleLine);
|
||||
|
||||
// convert text: vector<string> --> string
|
||||
std::wstring textData;
|
||||
for (const auto& text : bufferData.text)
|
||||
{
|
||||
textData += text;
|
||||
}
|
||||
|
||||
const auto bgColor = _terminal->GetAttributeColors({}).second;
|
||||
|
||||
// convert text to HTML format
|
||||
// GH#5347 - Don't provide a title for the generated HTML, as many
|
||||
// web applications will paste the title first, followed by the HTML
|
||||
// content, which is unexpected.
|
||||
const auto htmlData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::HTML) ?
|
||||
TextBuffer::GenHTML(bufferData,
|
||||
_actualFont.GetUnscaledSize().height,
|
||||
_actualFont.GetFaceName(),
|
||||
bgColor) :
|
||||
"";
|
||||
|
||||
// convert to RTF format
|
||||
const auto rtfData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::RTF) ?
|
||||
TextBuffer::GenRTF(bufferData,
|
||||
_actualFont.GetUnscaledSize().height,
|
||||
_actualFont.GetFaceName(),
|
||||
bgColor) :
|
||||
"";
|
||||
const auto& [textData, htmlData, rtfData] = _terminal->RetrieveSelectedTextFromBuffer(singleLine, copyHtml, copyRtf);
|
||||
|
||||
// send data up for clipboard
|
||||
_CopyToClipboardHandlers(*this,
|
||||
winrt::make<CopyToClipboardEventArgs>(winrt::hstring{ textData },
|
||||
winrt::to_hstring(htmlData),
|
||||
winrt::to_hstring(rtfData),
|
||||
formats));
|
||||
copyFormats));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1612,24 +1584,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return _terminal->IsSelectionActive();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Checks if the currently active selection spans multiple lines
|
||||
// Return Value:
|
||||
// - true if selection is multi-line
|
||||
bool ControlCore::HasMultiLineSelection() const
|
||||
{
|
||||
const auto lock = _terminal->LockForReading();
|
||||
assert(_terminal->IsSelectionActive()); // should only be called when selection is active
|
||||
return _terminal->GetSelectionAnchor().y != _terminal->GetSelectionEnd().y;
|
||||
}
|
||||
|
||||
bool ControlCore::CopyOnSelect() const
|
||||
{
|
||||
return _settings->CopyOnSelect();
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> ControlCore::SelectedText(bool trimTrailingWhitespace) const
|
||||
winrt::hstring ControlCore::SelectedText(bool trimTrailingWhitespace) const
|
||||
{
|
||||
// RetrieveSelectedTextFromBuffer will lock while it's reading
|
||||
const auto lock = _terminal->LockForReading();
|
||||
const auto internalResult{ _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text };
|
||||
|
||||
auto result = winrt::single_threaded_vector<winrt::hstring>();
|
||||
|
||||
for (const auto& row : internalResult)
|
||||
{
|
||||
result.Append(winrt::hstring{ row });
|
||||
}
|
||||
return result;
|
||||
const auto internalResult{ _terminal->RetrieveSelectedTextFromBuffer(!trimTrailingWhitespace) };
|
||||
return winrt::hstring{ internalResult.plainText };
|
||||
}
|
||||
|
||||
::Microsoft::Console::Render::IRenderData* ControlCore::GetRenderData() const
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// - ControlCore.h
|
||||
//
|
||||
// Abstract:
|
||||
// - This encapsulates a `Terminal` instance, a `DxEngine` and `Renderer`, and
|
||||
// - This encapsulates a `Terminal` instance, a `AtlasEngine` and `Renderer`, and
|
||||
// an `ITerminalConnection`. This is intended to be everything that someone
|
||||
// might need to stand up a terminal instance in a control, but without any
|
||||
// regard for how the UX works.
|
||||
@@ -156,7 +156,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
int BufferHeight() const;
|
||||
|
||||
bool HasSelection() const;
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
|
||||
bool HasMultiLineSelection() const;
|
||||
winrt::hstring SelectedText(bool trimTrailingWhitespace) const;
|
||||
|
||||
bool BracketedPasteEnabled() const noexcept;
|
||||
|
||||
@@ -315,6 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
winrt::hstring _actualFontFaceName;
|
||||
bool _builtinGlyphs = true;
|
||||
CSSLengthPercentage _cellWidth;
|
||||
CSSLengthPercentage _cellHeight;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "InteractivityAutomationPeer.h"
|
||||
|
||||
#include "ControlInteractivity.g.cpp"
|
||||
#include "TermControl.h"
|
||||
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace ::Microsoft::Console::VirtualTerminal;
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "HwndTerminal.hpp"
|
||||
#include <windowsx.h>
|
||||
#include <DefaultSettings.h>
|
||||
#include "../../types/viewport.cpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
|
||||
#include <DefaultSettings.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include "HwndTerminalAutomationPeer.hpp"
|
||||
#include "../../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../../renderer/atlas/AtlasEngine.h"
|
||||
#include "../../renderer/base/renderer.hpp"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
#include "../../types/viewport.cpp"
|
||||
|
||||
using namespace ::Microsoft::Console::VirtualTerminal;
|
||||
using namespace ::Microsoft::Terminal::Core;
|
||||
|
||||
static LPCWSTR term_window_class = L"HwndTerminalClass";
|
||||
@@ -102,22 +109,23 @@ try
|
||||
}
|
||||
break;
|
||||
case WM_RBUTTONDOWN:
|
||||
if (publicTerminal->_terminal && publicTerminal->_terminal->IsSelectionActive())
|
||||
try
|
||||
{
|
||||
try
|
||||
if (publicTerminal->_terminal)
|
||||
{
|
||||
const auto lock = publicTerminal->_terminal->LockForWriting();
|
||||
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
|
||||
LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData, true));
|
||||
publicTerminal->_ClearSelection();
|
||||
if (publicTerminal->_terminal->IsSelectionActive())
|
||||
{
|
||||
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, true, true);
|
||||
LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData.plainText, bufferData.html, bufferData.rtf));
|
||||
publicTerminal->_ClearSelection();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
else
|
||||
{
|
||||
publicTerminal->_PasteTextFromClipboard();
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
CATCH_LOG();
|
||||
case WM_DESTROY:
|
||||
// Release Terminal's hwnd so Teardown doesn't try to destroy it again
|
||||
publicTerminal->_hwnd.release();
|
||||
@@ -207,10 +215,10 @@ HRESULT HwndTerminal::Initialize()
|
||||
RETURN_HR_IF_NULL(E_POINTER, localPointerToThread);
|
||||
RETURN_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
RETURN_IF_FAILED(dxEngine->SetHwnd(_hwnd.get()));
|
||||
RETURN_IF_FAILED(dxEngine->Enable());
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
RETURN_IF_FAILED(engine->SetHwnd(_hwnd.get()));
|
||||
RETURN_IF_FAILED(engine->Enable());
|
||||
_renderer->AddRenderEngine(engine.get());
|
||||
|
||||
_UpdateFont(USER_DEFAULT_SCREEN_DPI);
|
||||
RECT windowRect;
|
||||
@@ -221,9 +229,9 @@ HRESULT HwndTerminal::Initialize()
|
||||
// Fist set up the dx engine with the window size in pixels.
|
||||
// Then, using the font, get the number of characters that can fit.
|
||||
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
|
||||
RETURN_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
RETURN_IF_FAILED(engine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
|
||||
_renderEngine = std::move(dxEngine);
|
||||
_renderEngine = std::move(engine);
|
||||
|
||||
_terminal->Create({ 80, 25 }, 9001, *_renderer);
|
||||
_terminal->SetWriteInputCallback([=](std::wstring_view input) noexcept { _WriteTextToConnection(input); });
|
||||
@@ -666,20 +674,14 @@ try
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TextBuffer::TextAndColor bufferData;
|
||||
std::wstring selectedText;
|
||||
{
|
||||
const auto lock = publicTerminal->_terminal->LockForWriting();
|
||||
bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
|
||||
auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
|
||||
selectedText = std::move(bufferData.plainText);
|
||||
publicTerminal->_ClearSelection();
|
||||
}
|
||||
|
||||
// convert text: vector<string> --> string
|
||||
std::wstring selectedText;
|
||||
for (const auto& text : bufferData.text)
|
||||
{
|
||||
selectedText += text;
|
||||
}
|
||||
|
||||
auto returnText = wil::make_cotaskmem_string_nothrow(selectedText.c_str());
|
||||
return returnText.release();
|
||||
}
|
||||
@@ -752,7 +754,7 @@ try
|
||||
ScreenToClient(_hwnd.get(), cursorPosition.as_win32_point());
|
||||
}
|
||||
|
||||
const TerminalInput::MouseButtonState state{
|
||||
const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state{
|
||||
WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed),
|
||||
WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed),
|
||||
WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed)
|
||||
@@ -897,7 +899,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
|
||||
[[gsl::suppress(bounds .3)]] renderSettings.SetColorTableEntry(tableIndex, gsl::at(theme.ColorTable, tableIndex));
|
||||
}
|
||||
|
||||
publicTerminal->_terminal->SetCursorStyle(static_cast<DispatchTypes::CursorStyle>(theme.CursorStyle));
|
||||
publicTerminal->_terminal->SetCursorStyle(static_cast<Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle>(theme.CursorStyle));
|
||||
|
||||
publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, static_cast<float>(fontSize), CP_UTF8 };
|
||||
publicTerminal->_UpdateFont(newDpi);
|
||||
@@ -963,22 +965,16 @@ void __stdcall TerminalKillFocus(void* terminal)
|
||||
// Routine Description:
|
||||
// - Copies the text given onto the global system clipboard.
|
||||
// Arguments:
|
||||
// - rows - Rows of text data to copy
|
||||
// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise
|
||||
HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting)
|
||||
// - text - selected text in plain-text format
|
||||
// - htmlData - selected text in HTML format
|
||||
// - rtfData - selected text in RTF format
|
||||
HRESULT HwndTerminal::_CopyTextToSystemClipboard(const std::wstring& text, const std::string& htmlData, const std::string& rtfData) const
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
|
||||
std::wstring finalString;
|
||||
|
||||
// Concatenate strings into one giant string to put onto the clipboard.
|
||||
for (const auto& str : rows.text)
|
||||
{
|
||||
finalString += str;
|
||||
}
|
||||
|
||||
// allocate the final clipboard data
|
||||
const auto cchNeeded = finalString.size() + 1;
|
||||
const auto cchNeeded = text.size() + 1;
|
||||
const auto cbNeeded = sizeof(wchar_t) * cchNeeded;
|
||||
wil::unique_hglobal globalHandle(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeeded));
|
||||
RETURN_LAST_ERROR_IF_NULL(globalHandle.get());
|
||||
@@ -988,7 +984,7 @@ try
|
||||
|
||||
// The pattern gets a bit strange here because there's no good wil built-in for global lock of this type.
|
||||
// Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock).
|
||||
const auto hr = StringCchCopyW(pwszClipboard, cchNeeded, finalString.data());
|
||||
const auto hr = StringCchCopyW(pwszClipboard, cchNeeded, text.data());
|
||||
GlobalUnlock(globalHandle.get());
|
||||
RETURN_IF_FAILED(hr);
|
||||
|
||||
@@ -1003,21 +999,14 @@ try
|
||||
RETURN_LAST_ERROR_IF(!EmptyClipboard());
|
||||
RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get()));
|
||||
|
||||
if (fAlsoCopyFormatting)
|
||||
if (!htmlData.empty())
|
||||
{
|
||||
const auto& fontData = _actualFont;
|
||||
const int iFontHeightPoints = fontData.GetUnscaledSize().height; // this renderer uses points already
|
||||
COLORREF bgColor;
|
||||
{
|
||||
const auto lock = _terminal->LockForReading();
|
||||
bgColor = _terminal->GetAttributeColors({}).second;
|
||||
}
|
||||
RETURN_IF_FAILED(_CopyToSystemClipboard(htmlData, L"HTML Format"));
|
||||
}
|
||||
|
||||
auto HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor);
|
||||
_CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format");
|
||||
|
||||
auto RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor);
|
||||
_CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format");
|
||||
if (!rtfData.empty())
|
||||
{
|
||||
RETURN_IF_FAILED(_CopyToSystemClipboard(rtfData, L"Rich Text Format"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,7 +1024,7 @@ CATCH_RETURN()
|
||||
// Arguments:
|
||||
// - stringToCopy - The string to copy
|
||||
// - lpszFormat - the name of the format
|
||||
HRESULT HwndTerminal::_CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat)
|
||||
HRESULT HwndTerminal::_CopyToSystemClipboard(const std::string& stringToCopy, LPCWSTR lpszFormat) const
|
||||
{
|
||||
const auto cbData = stringToCopy.size() + 1; // +1 for '\0'
|
||||
if (cbData)
|
||||
|
||||
@@ -3,14 +3,31 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
#include "../../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../../buffer/out/textBuffer.hpp"
|
||||
#include "../../renderer/inc/FontInfoDesired.hpp"
|
||||
#include "../../types/IControlAccessibilityInfo.h"
|
||||
#include "HwndTerminalAutomationPeer.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
namespace Microsoft::Console::Render::Atlas
|
||||
{
|
||||
class AtlasEngine;
|
||||
}
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
using AtlasEngine = Atlas::AtlasEngine;
|
||||
class IRenderData;
|
||||
class Renderer;
|
||||
class UiaEngine;
|
||||
}
|
||||
|
||||
namespace Microsoft::Terminal::Core
|
||||
{
|
||||
class Terminal;
|
||||
}
|
||||
|
||||
class FontInfo;
|
||||
class FontInfoDesired;
|
||||
class HwndTerminalAutomationPeer;
|
||||
|
||||
// Keep in sync with TerminalTheme.cs
|
||||
typedef struct _TerminalTheme
|
||||
@@ -79,7 +96,7 @@ private:
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
|
||||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
|
||||
std::unique_ptr<::Microsoft::Console::Render::AtlasEngine> _renderEngine;
|
||||
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
|
||||
|
||||
bool _focused{ false };
|
||||
@@ -109,8 +126,8 @@ private:
|
||||
|
||||
void _UpdateFont(int newDpi);
|
||||
void _WriteTextToConnection(const std::wstring_view text) noexcept;
|
||||
HRESULT _CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting);
|
||||
HRESULT _CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat);
|
||||
HRESULT _CopyTextToSystemClipboard(const std::wstring& text, const std::string& htmlData, const std::string& rtfData) const;
|
||||
HRESULT _CopyToSystemClipboard(const std::string& stringToCopy, LPCWSTR lpszFormat) const;
|
||||
void _PasteTextFromClipboard() noexcept;
|
||||
|
||||
const unsigned int _NumberOfClicks(til::point clickPos, std::chrono::steady_clock::time_point clickTime) noexcept;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import "IKeyBindings.idl";
|
||||
import "IControlAppearance.idl";
|
||||
import "EventArgs.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
@@ -34,20 +35,20 @@ namespace Microsoft.Terminal.Control
|
||||
Boolean EnableUnfocusedAcrylic;
|
||||
ScrollbarState ScrollState { get; };
|
||||
|
||||
Boolean UseAtlasEngine { get; };
|
||||
|
||||
String FontFace { get; };
|
||||
Single FontSize { get; };
|
||||
Windows.UI.Text.FontWeight FontWeight { get; };
|
||||
String Padding { get; };
|
||||
Windows.Foundation.Collections.IMap<String, UInt32> FontFeatures { get; };
|
||||
Windows.Foundation.Collections.IMap<String, Single> FontAxes { get; };
|
||||
Boolean EnableBuiltinGlyphs { get; };
|
||||
String CellWidth { get; };
|
||||
String CellHeight { get; };
|
||||
|
||||
Microsoft.Terminal.Control.IKeyBindings KeyBindings { get; };
|
||||
|
||||
Boolean CopyOnSelect { get; };
|
||||
Microsoft.Terminal.Control.CopyFormat CopyFormatting { get; };
|
||||
Boolean FocusFollowMouse { get; };
|
||||
|
||||
String Commandline { get; };
|
||||
|
||||
@@ -46,7 +46,8 @@ namespace Microsoft.Terminal.Control
|
||||
Int32 BufferHeight { get; };
|
||||
|
||||
Boolean HasSelection { get; };
|
||||
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
|
||||
Boolean HasMultiLineSelection { get; };
|
||||
String SelectedText(Boolean trimTrailingWhitespace);
|
||||
|
||||
Boolean BracketedPasteEnabled { get; };
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
// C++/winrt makes it difficult to share this idl between two projects,
|
||||
// Instead, we just pin the uuid and include it in both TermControl and App
|
||||
// If you update this one, please update TerminalApp\IDirectKeyListener.idl.
|
||||
// If you change this interface, please update the guid.
|
||||
// If you press F7 or Alt and get a runtime error, go make sure both copies are the same.
|
||||
[uuid("0ddf4edc-3fda-4dee-97ca-a417ee3dd510")] interface IDirectKeyListener {
|
||||
Boolean OnDirectKeyEvent(UInt32 vkey, UInt8 scanCode, Boolean down);
|
||||
};
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "ScrollBarVisualStateManager.h"
|
||||
#include "ScrollBarVisualStateManager.g.cpp"
|
||||
|
||||
#include "TermControl.h"
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml::Media;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
#include <winrt/Windows.UI.Xaml.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
|
||||
#include "TermControl.h"
|
||||
|
||||
#include "ScrollBarVisualStateManager.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
struct TermControl;
|
||||
|
||||
struct ScrollBarVisualStateManager : ScrollBarVisualStateManagerT<ScrollBarVisualStateManager>
|
||||
{
|
||||
bool GoToStateCore(winrt::Windows::UI::Xaml::Controls::Control const& control, winrt::Windows::UI::Xaml::FrameworkElement const& templateRoot, hstring const& stateName, winrt::Windows::UI::Xaml::VisualStateGroup const& group, winrt::Windows::UI::Xaml::VisualState const& state, bool useTransitions);
|
||||
|
||||
@@ -57,10 +57,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_isInternalScrollBarUpdate{ false },
|
||||
_autoScrollVelocity{ 0 },
|
||||
_autoScrollingPointerPoint{ std::nullopt },
|
||||
_autoScrollTimer{},
|
||||
_lastAutoScrollUpdateTime{ std::nullopt },
|
||||
_cursorTimer{},
|
||||
_blinkTimer{},
|
||||
_searchBox{ nullptr }
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -419,10 +416,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Currently we populate the search box only if a single line is selected.
|
||||
// Empirically, multi-line selection works as well on sample scenarios,
|
||||
// but since code paths differ, extra work is required to ensure correctness.
|
||||
auto bufferText = _core.SelectedText(true);
|
||||
if (bufferText.Size() == 1)
|
||||
if (!_core.HasMultiLineSelection())
|
||||
{
|
||||
const auto selectedLine{ bufferText.GetAt(0) };
|
||||
const auto selectedLine{ _core.SelectedText(true) };
|
||||
_searchBox->PopulateTextbox(selectedLine);
|
||||
}
|
||||
}
|
||||
@@ -1087,10 +1083,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
if (blinkTime != INFINITE)
|
||||
{
|
||||
// Create a timer
|
||||
DispatcherTimer cursorTimer;
|
||||
cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
|
||||
cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
|
||||
_cursorTimer.emplace(std::move(cursorTimer));
|
||||
_cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
|
||||
_cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
|
||||
// As of GH#6586, don't start the cursor timer immediately, and
|
||||
// don't show the cursor initially. We'll show the cursor and start
|
||||
// the timer when the control is first focused.
|
||||
@@ -1105,13 +1099,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_core.CursorOn(_focused || _displayCursorWhileBlurred());
|
||||
if (_displayCursorWhileBlurred())
|
||||
{
|
||||
_cursorTimer->Start();
|
||||
_cursorTimer.Start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user has disabled cursor blinking
|
||||
_cursorTimer = std::nullopt;
|
||||
_cursorTimer.Destroy();
|
||||
}
|
||||
|
||||
// Set up blinking attributes
|
||||
@@ -1120,16 +1113,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
if (animationsEnabled && blinkTime != INFINITE)
|
||||
{
|
||||
// Create a timer
|
||||
DispatcherTimer blinkTimer;
|
||||
blinkTimer.Interval(std::chrono::milliseconds(blinkTime));
|
||||
blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick });
|
||||
blinkTimer.Start();
|
||||
_blinkTimer.emplace(std::move(blinkTimer));
|
||||
_blinkTimer.Interval(std::chrono::milliseconds(blinkTime));
|
||||
_blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick });
|
||||
_blinkTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user has disabled blinking
|
||||
_blinkTimer = std::nullopt;
|
||||
_blinkTimer.Destroy();
|
||||
}
|
||||
|
||||
// Now that the renderer is set up, update the appearance for initialization
|
||||
@@ -1345,7 +1336,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Alt, so we should be ignoring the individual keydowns. The character
|
||||
// will be sent through the TSFInputControl. See GH#1401 for more
|
||||
// details
|
||||
if (modifiers.IsAltPressed() &&
|
||||
if (modifiers.IsAltPressed() && !modifiers.IsCtrlPressed() &&
|
||||
(vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9))
|
||||
{
|
||||
e.Handled(true);
|
||||
@@ -1498,7 +1489,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Manually show the cursor when a key is pressed. Restarting
|
||||
// the timer prevents flickering.
|
||||
_core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark);
|
||||
_cursorTimer->Start();
|
||||
_cursorTimer.Start();
|
||||
}
|
||||
|
||||
return handled;
|
||||
@@ -1973,12 +1964,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
// When the terminal focuses, show the cursor immediately
|
||||
_core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark);
|
||||
_cursorTimer->Start();
|
||||
_cursorTimer.Start();
|
||||
}
|
||||
|
||||
if (_blinkTimer)
|
||||
{
|
||||
_blinkTimer->Start();
|
||||
_blinkTimer.Start();
|
||||
}
|
||||
|
||||
// Only update the appearance here if an unfocused config exists - if an
|
||||
@@ -2021,13 +2012,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
if (_cursorTimer && !_displayCursorWhileBlurred())
|
||||
{
|
||||
_cursorTimer->Stop();
|
||||
_cursorTimer.Stop();
|
||||
_core.CursorOn(false);
|
||||
}
|
||||
|
||||
if (_blinkTimer)
|
||||
{
|
||||
_blinkTimer->Stop();
|
||||
_blinkTimer.Stop();
|
||||
}
|
||||
|
||||
// Check if there is an unfocused config we should set the appearance to
|
||||
@@ -2278,7 +2269,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
// Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||
TSFInputControl().Close();
|
||||
|
||||
// At the time of writing, closing the last tab of a window inexplicably
|
||||
// does not lead to the destruction of the remaining TermControl instance(s).
|
||||
// On Win10 we don't destroy window threads due to bugs in DesktopWindowXamlSource.
|
||||
// In turn, we leak TermControl instances. This results in constant HWND messages
|
||||
// while the thread is supposed to be idle. Stop these timers avoids this.
|
||||
_autoScrollTimer.Stop();
|
||||
_bellLightTimer.Stop();
|
||||
_cursorTimer.Stop();
|
||||
_blinkTimer.Stop();
|
||||
|
||||
if (!_detached)
|
||||
{
|
||||
@@ -2404,25 +2404,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// then use it to measure how much space the requested rows and columns
|
||||
// will take up.
|
||||
// TODO: MSFT:21254947 - use a static function to do this instead of
|
||||
// instantiating a DxEngine/AtlasEngine.
|
||||
// instantiating a AtlasEngine.
|
||||
// GH#10211 - UNDER NO CIRCUMSTANCE should this fail. If it does, the
|
||||
// whole app will crash instantaneously on launch, which is no good.
|
||||
float scale;
|
||||
if (settings.UseAtlasEngine())
|
||||
{
|
||||
auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
LOG_IF_FAILED(engine->UpdateDpi(dpi));
|
||||
LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont));
|
||||
scale = engine->GetScaling();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto engine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
LOG_IF_FAILED(engine->UpdateDpi(dpi));
|
||||
LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont));
|
||||
scale = engine->GetScaling();
|
||||
}
|
||||
const auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
LOG_IF_FAILED(engine->UpdateDpi(dpi));
|
||||
LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont));
|
||||
|
||||
const auto scale = engine->GetScaling();
|
||||
const auto actualFontSize = actualFont.GetSize();
|
||||
|
||||
// UWP XAML scrollbars aren't guaranteed to be the same size as the
|
||||
@@ -3129,20 +3118,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_bellDarkAnimation.Duration(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(TerminalWarningBellInterval)));
|
||||
}
|
||||
|
||||
// Similar to the animation, only initialize the timer here
|
||||
if (!_bellLightTimer)
|
||||
{
|
||||
_bellLightTimer = {};
|
||||
_bellLightTimer.Interval(std::chrono::milliseconds(TerminalWarningBellInterval));
|
||||
_bellLightTimer.Tick({ get_weak(), &TermControl::_BellLightOff });
|
||||
}
|
||||
|
||||
Windows::Foundation::Numerics::float2 zeroSize{ 0, 0 };
|
||||
// If the grid has 0 size or if the bell timer is
|
||||
// already active, do nothing
|
||||
if (RootGrid().ActualSize() != zeroSize && !_bellLightTimer.IsEnabled())
|
||||
{
|
||||
// Start the timer, when the timer ticks we switch off the light
|
||||
_bellLightTimer.Interval(std::chrono::milliseconds(TerminalWarningBellInterval));
|
||||
_bellLightTimer.Tick({ get_weak(), &TermControl::_BellLightOff });
|
||||
_bellLightTimer.Start();
|
||||
|
||||
// Switch on the light and animate the intensity to fade out
|
||||
@@ -3162,15 +3144,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void TermControl::_BellLightOff(const Windows::Foundation::IInspectable& /* sender */,
|
||||
const Windows::Foundation::IInspectable& /* e */)
|
||||
{
|
||||
if (_bellLightTimer)
|
||||
{
|
||||
// Stop the timer and switch off the light
|
||||
_bellLightTimer.Stop();
|
||||
// Stop the timer and switch off the light
|
||||
_bellLightTimer.Stop();
|
||||
|
||||
if (!_IsClosing())
|
||||
{
|
||||
VisualBellLight::SetIsTarget(RootGrid(), false);
|
||||
}
|
||||
if (!_IsClosing())
|
||||
{
|
||||
VisualBellLight::SetIsTarget(RootGrid(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3496,7 +3475,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
return _core.HasSelection();
|
||||
}
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> TermControl::SelectedText(bool trimTrailingWhitespace) const
|
||||
bool TermControl::HasMultiLineSelection() const
|
||||
{
|
||||
return _core.HasMultiLineSelection();
|
||||
}
|
||||
winrt::hstring TermControl::SelectedText(bool trimTrailingWhitespace) const
|
||||
{
|
||||
return _core.SelectedText(trimTrailingWhitespace);
|
||||
}
|
||||
@@ -3729,9 +3712,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
// If we should be ALWAYS displaying the cursor, turn it on and start blinking.
|
||||
_core.CursorOn(true);
|
||||
if (_cursorTimer.has_value())
|
||||
if (_cursorTimer)
|
||||
{
|
||||
_cursorTimer->Start();
|
||||
_cursorTimer.Start();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3740,9 +3723,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// blinking. (if we're focused, then we're already doing the right
|
||||
// thing)
|
||||
const auto focused = FocusState() != FocusState::Unfocused;
|
||||
if (!focused && _cursorTimer.has_value())
|
||||
if (!focused && _cursorTimer)
|
||||
{
|
||||
_cursorTimer->Stop();
|
||||
_cursorTimer.Stop();
|
||||
}
|
||||
_core.CursorOn(focused);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "XamlLights.h"
|
||||
#include "EventArgs.h"
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
#include "../../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../buffer/out/search.h"
|
||||
@@ -72,7 +71,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
int BufferHeight() const;
|
||||
|
||||
bool HasSelection() const;
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
|
||||
bool HasMultiLineSelection() const;
|
||||
winrt::hstring SelectedText(bool trimTrailingWhitespace) const;
|
||||
|
||||
bool BracketedPasteEnabled() const noexcept;
|
||||
|
||||
@@ -236,16 +236,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// viewport. View is then scrolled to 'follow' the cursor.
|
||||
double _autoScrollVelocity;
|
||||
std::optional<Windows::UI::Input::PointerPoint> _autoScrollingPointerPoint;
|
||||
Windows::UI::Xaml::DispatcherTimer _autoScrollTimer;
|
||||
SafeDispatcherTimer _autoScrollTimer;
|
||||
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime;
|
||||
bool _pointerPressedInBounds{ false };
|
||||
|
||||
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellLightAnimation{ nullptr };
|
||||
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellDarkAnimation{ nullptr };
|
||||
Windows::UI::Xaml::DispatcherTimer _bellLightTimer{ nullptr };
|
||||
SafeDispatcherTimer _bellLightTimer;
|
||||
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer;
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _blinkTimer;
|
||||
SafeDispatcherTimer _cursorTimer;
|
||||
SafeDispatcherTimer _blinkTimer;
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
bool _showMarksInScrollbar{ false };
|
||||
|
||||
@@ -4,13 +4,19 @@
|
||||
import "IMouseWheelListener.idl";
|
||||
import "IControlSettings.idl";
|
||||
import "ControlInteractivity.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
import "EventArgs.idl";
|
||||
import "ICoreState.idl";
|
||||
import "ControlCore.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
// This matches the definition in M.T.UI.
|
||||
// Having it here prevents us from having to refer to M.T.UI in **all consuming projects**.
|
||||
// WinRT is a trip.
|
||||
[uuid("0ddf4edc-3fda-4dee-97ca-a417ee3dd510")]
|
||||
interface IDirectKeyListener {
|
||||
Boolean OnDirectKeyEvent(UInt32 vkey, UInt8 scanCode, Boolean down);
|
||||
}
|
||||
|
||||
enum CursorDisplayState
|
||||
{
|
||||
|
||||
@@ -28,7 +28,6 @@ Modifications:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TermControl.h"
|
||||
#include "ControlInteractivity.h"
|
||||
#include "TermControlAutomationPeer.g.h"
|
||||
#include "../types/TermControlUiaProvider.hpp"
|
||||
@@ -37,6 +36,8 @@ Modifications:
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
struct TermControl;
|
||||
|
||||
struct TermControlAutomationPeer :
|
||||
public TermControlAutomationPeerT<TermControlAutomationPeer>,
|
||||
::Microsoft::Console::Types::IUiaEventDispatcher
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
|
||||
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
@@ -122,7 +122,6 @@
|
||||
<Midl Include="ControlInteractivity.idl" />
|
||||
<Midl Include="ScrollBarVisualStateManager.idl" />
|
||||
<Midl Include="ICoreState.idl" />
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="KeyChord.idl" />
|
||||
<Midl Include="EventArgs.idl" />
|
||||
<Midl Include="IKeyBindings.idl" />
|
||||
@@ -166,7 +165,6 @@
|
||||
<ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\renderer\base\lib\base.vcxproj" />
|
||||
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj" />
|
||||
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj" />
|
||||
<ProjectReference Include="..\..\renderer\uia\lib\uia.vcxproj" />
|
||||
<ProjectReference Include="..\..\terminal\parser\lib\parser.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\terminal\input\lib\terminalinput.vcxproj" />
|
||||
@@ -179,6 +177,10 @@
|
||||
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\UIHelpers\UIHelpers.vcxproj">
|
||||
<Project>{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}</Project>
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<!-- ====================== Compiler & Linker Flags ===================== -->
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
@@ -92,8 +92,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const
|
||||
{
|
||||
// Call the function off of the underlying UiaTextRange.
|
||||
VARIANT result;
|
||||
THROW_IF_FAILED(_uiaProvider->GetAttributeValue(textAttributeId, &result));
|
||||
wil::unique_variant result;
|
||||
THROW_IF_FAILED(_uiaProvider->GetAttributeValue(textAttributeId, result.addressof()));
|
||||
|
||||
// Convert the resulting VARIANT into a format that is consumable by XAML.
|
||||
switch (result.vt)
|
||||
@@ -189,9 +189,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
winrt::hstring XamlUiaTextRange::GetText(int32_t maxLength) const
|
||||
{
|
||||
BSTR returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->GetText(maxLength, &returnVal));
|
||||
return winrt::to_hstring(returnVal);
|
||||
wil::unique_bstr returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->GetText(maxLength, returnVal.put()));
|
||||
return winrt::hstring{ returnVal.get(), SysStringLen(returnVal.get()) };
|
||||
}
|
||||
|
||||
int32_t XamlUiaTextRange::Move(XamlAutomation::TextUnit unit,
|
||||
|
||||
@@ -65,6 +65,11 @@
|
||||
<Private>true</Private>
|
||||
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\UIHelpers\UIHelpers.vcxproj">
|
||||
<Project>{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}</Project>
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
|
||||
#include <winrt/Microsoft.Terminal.Core.h>
|
||||
|
||||
#include <winrt/Microsoft.Terminal.UI.h>
|
||||
|
||||
#include <windows.ui.xaml.media.dxinterop.h>
|
||||
|
||||
#include <TraceLoggingProvider.h>
|
||||
@@ -73,7 +75,8 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalControlProvider);
|
||||
#include <til/mutex.h>
|
||||
#include <til/winrt.h>
|
||||
|
||||
#include "ThrottledFunc.h"
|
||||
#include <SafeDispatcherTimer.h>
|
||||
#include <ThrottledFunc.h>
|
||||
|
||||
#include <cppwinrt_utils.h>
|
||||
#include <wil/cppwinrt_helpers.h> // must go after the CoreDispatcher type is defined
|
||||
|
||||
@@ -293,6 +293,13 @@ public:
|
||||
End = 0x2
|
||||
};
|
||||
|
||||
struct TextCopyData
|
||||
{
|
||||
std::wstring plainText;
|
||||
std::string html;
|
||||
std::string rtf;
|
||||
};
|
||||
|
||||
void MultiClickSelection(const til::point viewportPos, SelectionExpansion expansionMode);
|
||||
void SetSelectionAnchor(const til::point position);
|
||||
void SetSelectionEnd(const til::point position, std::optional<SelectionExpansion> newExpansionMode = std::nullopt);
|
||||
@@ -311,7 +318,7 @@ public:
|
||||
til::point SelectionEndForRendering() const;
|
||||
const SelectionEndpoint SelectionEndpointTarget() const noexcept;
|
||||
|
||||
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace);
|
||||
TextCopyData RetrieveSelectedTextFromBuffer(const bool singleLine, const bool html = false, const bool rtf = false) const;
|
||||
#pragma endregion
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -83,7 +83,7 @@ std::vector<til::inclusive_rect> Terminal::_GetSearchSelectionRects(Microsoft::C
|
||||
for (auto selection = lowerIt; selection != upperIt; ++selection)
|
||||
{
|
||||
const auto start = til::point{ selection->left, selection->top };
|
||||
const auto end = til::point{ selection->right, selection->top };
|
||||
const auto end = til::point{ selection->right, selection->bottom };
|
||||
const auto adj = _activeBuffer().GetTextRects(start, end, _blockSelection, false);
|
||||
for (auto a : adj)
|
||||
{
|
||||
@@ -867,27 +867,53 @@ void Terminal::ClearSelection()
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get wstring text from highlighted portion of text buffer
|
||||
// - Get text from highlighted portion of text buffer
|
||||
// - Optionally, get the highlighted text in HTML and RTF formats
|
||||
// Arguments:
|
||||
// - singleLine: collapse all of the text to one line
|
||||
// - singleLine: collapse all of the text to one line. (Turns off trailing whitespace trimming)
|
||||
// - html: also get text in HTML format
|
||||
// - rtf: also get text in RTF format
|
||||
// Return Value:
|
||||
// - wstring text from buffer. If extended to multiple lines, each line is separated by \r\n
|
||||
const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool singleLine)
|
||||
// - Plain and formatted selected text from buffer. Empty string represents no data for that format.
|
||||
// - If extended to multiple lines, each line is separated by \r\n
|
||||
Terminal::TextCopyData Terminal::RetrieveSelectedTextFromBuffer(const bool singleLine, const bool html, const bool rtf) const
|
||||
{
|
||||
const auto selectionRects = _GetSelectionRects();
|
||||
TextCopyData data;
|
||||
|
||||
if (!IsSelectionActive())
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
const auto GetAttributeColors = [&](const auto& attr) {
|
||||
return _renderSettings.GetAttributeColors(attr);
|
||||
const auto [fg, bg] = _renderSettings.GetAttributeColors(attr);
|
||||
const auto ul = _renderSettings.GetAttributeUnderlineColor(attr);
|
||||
return std::tuple{ fg, bg, ul };
|
||||
};
|
||||
|
||||
// GH#6740: Block selection should preserve the visual structure:
|
||||
// - CRLFs need to be added - so the lines structure is preserved
|
||||
// - We should apply formatting above to wrapped rows as well (newline should be added).
|
||||
// GH#9706: Trimming of trailing white-spaces in block selection is configurable.
|
||||
const auto includeCRLF = !singleLine || _blockSelection;
|
||||
const auto trimTrailingWhitespace = !singleLine && (!_blockSelection || _trimBlockSelection);
|
||||
const auto formatWrappedRows = _blockSelection;
|
||||
return _activeBuffer().GetText(includeCRLF, trimTrailingWhitespace, selectionRects, GetAttributeColors, formatWrappedRows);
|
||||
const auto& textBuffer = _activeBuffer();
|
||||
|
||||
const auto req = TextBuffer::CopyRequest::FromConfig(textBuffer, _selection->start, _selection->end, singleLine, _blockSelection, _trimBlockSelection);
|
||||
data.plainText = textBuffer.GetPlainText(req);
|
||||
|
||||
if (html || rtf)
|
||||
{
|
||||
const auto bgColor = _renderSettings.GetAttributeColors({}).second;
|
||||
const auto isIntenseBold = _renderSettings.GetRenderMode(::Microsoft::Console::Render::RenderSettings::Mode::IntenseIsBold);
|
||||
const auto fontSizePt = _fontInfo.GetUnscaledSize().height; // already in points
|
||||
const auto& fontName = _fontInfo.GetFaceName();
|
||||
|
||||
if (html)
|
||||
{
|
||||
data.html = textBuffer.GenHTML(req, fontSizePt, fontName, bgColor, isIntenseBold, GetAttributeColors);
|
||||
}
|
||||
if (rtf)
|
||||
{
|
||||
data.rtf = textBuffer.GenRTF(req, fontSizePt, fontName, bgColor, isIntenseBold, GetAttributeColors);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -43,9 +43,6 @@
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\renderer\dx\lib\dx.vcxproj">
|
||||
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\audio\midi\lib\midi.vcxproj">
|
||||
<Project>{3c67784e-1453-49c2-9660-483e2cc7f7ad}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
@@ -4,108 +4,14 @@
|
||||
#include "pch.h"
|
||||
#include "Actions.h"
|
||||
#include "Actions.g.cpp"
|
||||
#include "KeyBindingViewModel.g.cpp"
|
||||
#include "ActionsPageNavigationState.g.cpp"
|
||||
#include "LibraryResources.h"
|
||||
#include "../TerminalSettingsModel/AllShortcutActions.h"
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Xaml::Data;
|
||||
using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
KeyBindingViewModel::KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector<hstring>& availableActions) :
|
||||
KeyBindingViewModel(nullptr, availableActions.First().Current(), availableActions) {}
|
||||
|
||||
KeyBindingViewModel::KeyBindingViewModel(const Control::KeyChord& keys, const hstring& actionName, const IObservableVector<hstring>& availableActions) :
|
||||
_CurrentKeys{ keys },
|
||||
_KeyChordText{ KeyChordSerialization::ToString(keys) },
|
||||
_CurrentAction{ actionName },
|
||||
_ProposedAction{ box_value(actionName) },
|
||||
_AvailableActions{ availableActions }
|
||||
{
|
||||
// Add a property changed handler to our own property changed event.
|
||||
// This propagates changes from the settings model to anybody listening to our
|
||||
// unique view model members.
|
||||
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto viewModelProperty{ args.PropertyName() };
|
||||
if (viewModelProperty == L"CurrentKeys")
|
||||
{
|
||||
_KeyChordText = KeyChordSerialization::ToString(_CurrentKeys);
|
||||
_NotifyChanges(L"KeyChordText");
|
||||
}
|
||||
else if (viewModelProperty == L"IsContainerFocused" ||
|
||||
viewModelProperty == L"IsEditButtonFocused" ||
|
||||
viewModelProperty == L"IsHovered" ||
|
||||
viewModelProperty == L"IsAutomationPeerAttached" ||
|
||||
viewModelProperty == L"IsInEditMode")
|
||||
{
|
||||
_NotifyChanges(L"ShowEditButton");
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentAction")
|
||||
{
|
||||
_NotifyChanges(L"Name");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hstring KeyBindingViewModel::EditButtonName() const noexcept { return RS_(L"Actions_EditButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
hstring KeyBindingViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
hstring KeyBindingViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
hstring KeyBindingViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
|
||||
bool KeyBindingViewModel::ShowEditButton() const noexcept
|
||||
{
|
||||
return (IsContainerFocused() || IsEditButtonFocused() || IsHovered() || IsAutomationPeerAttached()) && !IsInEditMode();
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::ToggleEditMode()
|
||||
{
|
||||
// toggle edit mode
|
||||
IsInEditMode(!_IsInEditMode);
|
||||
if (_IsInEditMode)
|
||||
{
|
||||
// if we're in edit mode,
|
||||
// - pre-populate the text box with the current keys
|
||||
// - reset the combo box with the current action
|
||||
ProposedKeys(_CurrentKeys);
|
||||
ProposedAction(box_value(_CurrentAction));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::AttemptAcceptChanges()
|
||||
{
|
||||
AttemptAcceptChanges(_ProposedKeys);
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::AttemptAcceptChanges(const Control::KeyChord newKeys)
|
||||
{
|
||||
const auto args{ make_self<ModifyKeyBindingEventArgs>(_CurrentKeys, // OldKeys
|
||||
newKeys, // NewKeys
|
||||
_IsNewlyAdded ? hstring{} : _CurrentAction, // OldAction
|
||||
unbox_value<hstring>(_ProposedAction)) }; // NewAction
|
||||
_ModifyKeyBindingRequestedHandlers(*this, *args);
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::CancelChanges()
|
||||
{
|
||||
if (_IsNewlyAdded)
|
||||
{
|
||||
_DeleteNewlyAddedKeyBindingHandlers(*this, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleEditMode();
|
||||
}
|
||||
}
|
||||
|
||||
Actions::Actions()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -115,277 +21,55 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
Automation::Peers::AutomationPeer Actions::OnCreateAutomationPeer()
|
||||
{
|
||||
_AutomationPeerAttached = true;
|
||||
for (const auto& kbdVM : _KeyBindingList)
|
||||
{
|
||||
// To create a more accessible experience, we want the "edit" buttons to _always_
|
||||
// appear when a screen reader is attached. This ensures that the edit buttons are
|
||||
// accessible via the UIA tree.
|
||||
get_self<KeyBindingViewModel>(kbdVM)->IsAutomationPeerAttached(_AutomationPeerAttached);
|
||||
}
|
||||
_ViewModel.OnAutomationPeerAttached();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Actions::OnNavigatedTo(const NavigationEventArgs& e)
|
||||
{
|
||||
_State = e.Parameter().as<Editor::ActionsPageNavigationState>();
|
||||
_ViewModel = e.Parameter().as<Editor::ActionsViewModel>();
|
||||
|
||||
// Populate AvailableActionAndArgs
|
||||
_AvailableActionMap = single_threaded_map<hstring, Model::ActionAndArgs>();
|
||||
std::vector<hstring> availableActionAndArgs;
|
||||
for (const auto& [name, actionAndArgs] : _State.Settings().ActionMap().AvailableActions())
|
||||
{
|
||||
availableActionAndArgs.push_back(name);
|
||||
_AvailableActionMap.Insert(name, actionAndArgs);
|
||||
}
|
||||
std::sort(begin(availableActionAndArgs), end(availableActionAndArgs));
|
||||
_AvailableActionAndArgs = single_threaded_observable_vector(std::move(availableActionAndArgs));
|
||||
// Subscribe to the view model's FocusContainer event.
|
||||
// Use the KeyBindingViewModel or index provided in the event to focus the corresponding container
|
||||
_ViewModel.FocusContainer([this](const auto& /*sender*/, const auto& args) {
|
||||
if (auto kbdVM{ args.try_as<KeyBindingViewModel>() })
|
||||
{
|
||||
if (const auto& container = KeyBindingsListView().ContainerFromItem(*kbdVM))
|
||||
{
|
||||
container.as<Controls::ListViewItem>().Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
else if (const auto& index = args.try_as<uint32_t>())
|
||||
{
|
||||
if (const auto& container = KeyBindingsListView().ContainerFromIndex(*index))
|
||||
{
|
||||
container.as<Controls::ListViewItem>().Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Convert the key bindings from our settings into a view model representation
|
||||
const auto& keyBindingMap{ _State.Settings().ActionMap().KeyBindings() };
|
||||
std::vector<Editor::KeyBindingViewModel> keyBindingList;
|
||||
keyBindingList.reserve(keyBindingMap.Size());
|
||||
for (const auto& [keys, cmd] : keyBindingMap)
|
||||
{
|
||||
// convert the cmd into a KeyBindingViewModel
|
||||
auto container{ make_self<KeyBindingViewModel>(keys, cmd.Name(), _AvailableActionAndArgs) };
|
||||
_RegisterEvents(container);
|
||||
keyBindingList.push_back(*container);
|
||||
}
|
||||
|
||||
std::sort(begin(keyBindingList), end(keyBindingList), KeyBindingViewModelComparator{});
|
||||
_KeyBindingList = single_threaded_observable_vector(std::move(keyBindingList));
|
||||
// Subscribe to the view model's UpdateBackground event.
|
||||
// The view model does not have access to the page resources, so it asks us
|
||||
// to update the key binding's container background
|
||||
_ViewModel.UpdateBackground([this](const auto& /*sender*/, const auto& args) {
|
||||
if (auto kbdVM{ args.try_as<KeyBindingViewModel>() })
|
||||
{
|
||||
if (kbdVM->IsInEditMode())
|
||||
{
|
||||
const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as<Windows::UI::Xaml::Media::Brush>() };
|
||||
kbdVM->ContainerBackground(containerBackground);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackground")).as<Windows::UI::Xaml::Media::Brush>() };
|
||||
kbdVM->ContainerBackground(containerBackground);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Actions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
// Create the new key binding and register all of the event handlers.
|
||||
auto kbdVM{ make_self<KeyBindingViewModel>(_AvailableActionAndArgs) };
|
||||
_RegisterEvents(kbdVM);
|
||||
kbdVM->DeleteNewlyAddedKeyBinding({ this, &Actions::_ViewModelDeleteNewlyAddedKeyBindingHandler });
|
||||
|
||||
// Manually add the editing background. This needs to be done in Actions not the view model.
|
||||
// We also have to do this manually because it hasn't been added to the list yet.
|
||||
kbdVM->IsInEditMode(true);
|
||||
const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as<Windows::UI::Xaml::Media::Brush>() };
|
||||
kbdVM->ContainerBackground(containerBackground);
|
||||
|
||||
// IMPORTANT: do this _after_ setting IsInEditMode. Otherwise, it'll get deleted immediately
|
||||
// by the PropertyChangedHandler below (where we delete any IsNewlyAdded items)
|
||||
kbdVM->IsNewlyAdded(true);
|
||||
_KeyBindingList.InsertAt(0, *kbdVM);
|
||||
}
|
||||
|
||||
void Actions::_ViewModelPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args)
|
||||
{
|
||||
const auto senderVM{ sender.as<Editor::KeyBindingViewModel>() };
|
||||
const auto propertyName{ args.PropertyName() };
|
||||
if (propertyName == L"IsInEditMode")
|
||||
{
|
||||
if (senderVM.IsInEditMode())
|
||||
{
|
||||
// Ensure that...
|
||||
// 1. we move focus to the edit mode controls
|
||||
// 2. any actions that were newly added are removed
|
||||
// 3. this is the only entry that is in edit mode
|
||||
for (int32_t i = _KeyBindingList.Size() - 1; i >= 0; --i)
|
||||
{
|
||||
const auto& kbdVM{ _KeyBindingList.GetAt(i) };
|
||||
if (senderVM == kbdVM)
|
||||
{
|
||||
// This is the view model entry that went into edit mode.
|
||||
// Move focus to the edit mode controls by
|
||||
// extracting the list view item container.
|
||||
const auto& container{ KeyBindingsListView().ContainerFromIndex(i).try_as<ListViewItem>() };
|
||||
container.Focus(FocusState::Programmatic);
|
||||
}
|
||||
else if (kbdVM.IsNewlyAdded())
|
||||
{
|
||||
// Remove any actions that were newly added
|
||||
_KeyBindingList.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exit edit mode for all other containers
|
||||
get_self<KeyBindingViewModel>(kbdVM)->DisableEditMode();
|
||||
}
|
||||
}
|
||||
|
||||
const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as<Windows::UI::Xaml::Media::Brush>() };
|
||||
get_self<KeyBindingViewModel>(senderVM)->ContainerBackground(containerBackground);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Focus on the list view item
|
||||
KeyBindingsListView().ContainerFromItem(senderVM).as<Controls::Control>().Focus(FocusState::Programmatic);
|
||||
|
||||
const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackground")).as<Windows::UI::Xaml::Media::Brush>() };
|
||||
get_self<KeyBindingViewModel>(senderVM)->ContainerBackground(containerBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actions::_ViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& keys)
|
||||
{
|
||||
// Update the settings model
|
||||
_State.Settings().ActionMap().DeleteKeyBinding(keys);
|
||||
|
||||
// Find the current container in our list and remove it.
|
||||
// This is much faster than rebuilding the entire ActionMap.
|
||||
uint32_t index;
|
||||
if (_KeyBindingList.IndexOf(senderVM, index))
|
||||
{
|
||||
_KeyBindingList.RemoveAt(index);
|
||||
|
||||
// Focus the new item at this index
|
||||
if (_KeyBindingList.Size() != 0)
|
||||
{
|
||||
const auto newFocusedIndex{ std::clamp(index, 0u, _KeyBindingList.Size() - 1) };
|
||||
KeyBindingsListView().ContainerFromIndex(newFocusedIndex).as<Controls::Control>().Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actions::_ViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args)
|
||||
{
|
||||
const auto isNewAction{ !args.OldKeys() && args.OldActionName().empty() };
|
||||
|
||||
auto applyChangesToSettingsModel = [=]() {
|
||||
// If the key chord was changed,
|
||||
// update the settings model and view model appropriately
|
||||
// NOTE: we still need to update the view model if we're working with a newly added action
|
||||
if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey())
|
||||
{
|
||||
if (!isNewAction)
|
||||
{
|
||||
// update settings model
|
||||
_State.Settings().ActionMap().RebindKeys(args.OldKeys(), args.NewKeys());
|
||||
}
|
||||
|
||||
// update view model
|
||||
auto senderVMImpl{ get_self<KeyBindingViewModel>(senderVM) };
|
||||
senderVMImpl->CurrentKeys(args.NewKeys());
|
||||
}
|
||||
|
||||
// If the action was changed,
|
||||
// update the settings model and view model appropriately
|
||||
// NOTE: no need to check for "isNewAction" here. <empty_string> != <action name> already.
|
||||
if (args.OldActionName() != args.NewActionName())
|
||||
{
|
||||
// convert the action's name into a view model.
|
||||
const auto& newAction{ _AvailableActionMap.Lookup(args.NewActionName()) };
|
||||
|
||||
// update settings model
|
||||
_State.Settings().ActionMap().RegisterKeyBinding(args.NewKeys(), newAction);
|
||||
|
||||
// update view model
|
||||
auto senderVMImpl{ get_self<KeyBindingViewModel>(senderVM) };
|
||||
senderVMImpl->CurrentAction(args.NewActionName());
|
||||
senderVMImpl->IsNewlyAdded(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Check for this special case:
|
||||
// we're changing the key chord,
|
||||
// but the new key chord is already in use
|
||||
if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey())
|
||||
{
|
||||
const auto& conflictingCmd{ _State.Settings().ActionMap().GetActionByKeyChord(args.NewKeys()) };
|
||||
if (conflictingCmd)
|
||||
{
|
||||
// We're about to overwrite another key chord.
|
||||
// Display a confirmation dialog.
|
||||
TextBlock errorMessageTB{};
|
||||
errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage"));
|
||||
|
||||
const auto conflictingCmdName{ conflictingCmd.Name() };
|
||||
TextBlock conflictingCommandNameTB{};
|
||||
conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName));
|
||||
conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic);
|
||||
|
||||
TextBlock confirmationQuestionTB{};
|
||||
confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion"));
|
||||
|
||||
Button acceptBTN{};
|
||||
acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton")));
|
||||
acceptBTN.Click([=](auto&, auto&) {
|
||||
// remove conflicting key binding from list view
|
||||
const auto containerIndex{ _GetContainerIndexByKeyChord(args.NewKeys()) };
|
||||
_KeyBindingList.RemoveAt(*containerIndex);
|
||||
|
||||
// remove flyout
|
||||
senderVM.AcceptChangesFlyout().Hide();
|
||||
senderVM.AcceptChangesFlyout(nullptr);
|
||||
|
||||
// update settings model and view model
|
||||
applyChangesToSettingsModel();
|
||||
senderVM.ToggleEditMode();
|
||||
});
|
||||
|
||||
StackPanel flyoutStack{};
|
||||
flyoutStack.Children().Append(errorMessageTB);
|
||||
flyoutStack.Children().Append(conflictingCommandNameTB);
|
||||
flyoutStack.Children().Append(confirmationQuestionTB);
|
||||
flyoutStack.Children().Append(acceptBTN);
|
||||
|
||||
Flyout acceptChangesFlyout{};
|
||||
acceptChangesFlyout.Content(flyoutStack);
|
||||
senderVM.AcceptChangesFlyout(acceptChangesFlyout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// update settings model and view model
|
||||
applyChangesToSettingsModel();
|
||||
|
||||
// We NEED to toggle the edit mode here,
|
||||
// so that if nothing changed, we still exit
|
||||
// edit mode.
|
||||
senderVM.ToggleEditMode();
|
||||
}
|
||||
|
||||
void Actions::_ViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& /*args*/)
|
||||
{
|
||||
for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i)
|
||||
{
|
||||
const auto& kbdVM{ _KeyBindingList.GetAt(i) };
|
||||
if (kbdVM == senderVM)
|
||||
{
|
||||
_KeyBindingList.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - performs a search on KeyBindingList by key chord.
|
||||
// Arguments:
|
||||
// - keys - the associated key chord of the command we're looking for
|
||||
// Return Value:
|
||||
// - the index of the view model referencing the command. If the command doesn't exist, nullopt
|
||||
std::optional<uint32_t> Actions::_GetContainerIndexByKeyChord(const Control::KeyChord& keys)
|
||||
{
|
||||
for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i)
|
||||
{
|
||||
const auto kbdVM{ get_self<KeyBindingViewModel>(_KeyBindingList.GetAt(i)) };
|
||||
const auto& otherKeys{ kbdVM->CurrentKeys() };
|
||||
if (otherKeys && keys.Modifiers() == otherKeys.Modifiers() && keys.Vkey() == otherKeys.Vkey())
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO GH #6900:
|
||||
// an expedited search can be done if we use cmd.Name()
|
||||
// to quickly search through the sorted list.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Actions::_RegisterEvents(com_ptr<KeyBindingViewModel>& kbdVM)
|
||||
{
|
||||
kbdVM->PropertyChanged({ this, &Actions::_ViewModelPropertyChangedHandler });
|
||||
kbdVM->DeleteKeyBindingRequested({ this, &Actions::_ViewModelDeleteKeyBindingHandler });
|
||||
kbdVM->ModifyKeyBindingRequested({ this, &Actions::_ViewModelModifyKeyBindingHandler });
|
||||
kbdVM->IsAutomationPeerAttached(_AutomationPeerAttached);
|
||||
_ViewModel.AddNewKeybinding();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,109 +4,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Actions.g.h"
|
||||
#include "KeyBindingViewModel.g.h"
|
||||
#include "ActionsPageNavigationState.g.h"
|
||||
#include "ModifyKeyBindingEventArgs.g.h"
|
||||
#include "ActionsViewModel.h"
|
||||
#include "Utils.h"
|
||||
#include "ViewModelHelpers.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct KeyBindingViewModelComparator
|
||||
{
|
||||
bool operator()(const Editor::KeyBindingViewModel& lhs, const Editor::KeyBindingViewModel& rhs) const
|
||||
{
|
||||
return lhs.Name() < rhs.Name();
|
||||
}
|
||||
};
|
||||
|
||||
struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT<ModifyKeyBindingEventArgs>
|
||||
{
|
||||
public:
|
||||
ModifyKeyBindingEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys, const hstring oldActionName, const hstring newActionName) :
|
||||
_OldKeys{ oldKeys },
|
||||
_NewKeys{ newKeys },
|
||||
_OldActionName{ std::move(oldActionName) },
|
||||
_NewActionName{ std::move(newActionName) } {}
|
||||
|
||||
WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr);
|
||||
WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr);
|
||||
WINRT_PROPERTY(hstring, OldActionName);
|
||||
WINRT_PROPERTY(hstring, NewActionName);
|
||||
};
|
||||
|
||||
struct KeyBindingViewModel : KeyBindingViewModelT<KeyBindingViewModel>, ViewModelHelper<KeyBindingViewModel>
|
||||
{
|
||||
public:
|
||||
KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector<hstring>& availableActions);
|
||||
KeyBindingViewModel(const Control::KeyChord& keys, const hstring& name, const Windows::Foundation::Collections::IObservableVector<hstring>& availableActions);
|
||||
|
||||
hstring Name() const { return _CurrentAction; }
|
||||
hstring KeyChordText() const { return _KeyChordText; }
|
||||
|
||||
// UIA Text
|
||||
hstring EditButtonName() const noexcept;
|
||||
hstring CancelButtonName() const noexcept;
|
||||
hstring AcceptButtonName() const noexcept;
|
||||
hstring DeleteButtonName() const noexcept;
|
||||
|
||||
void EnterHoverMode() { IsHovered(true); };
|
||||
void ExitHoverMode() { IsHovered(false); };
|
||||
void ActionGotFocus() { IsContainerFocused(true); };
|
||||
void ActionLostFocus() { IsContainerFocused(false); };
|
||||
void EditButtonGettingFocus() { IsEditButtonFocused(true); };
|
||||
void EditButtonLosingFocus() { IsEditButtonFocused(false); };
|
||||
bool ShowEditButton() const noexcept;
|
||||
void ToggleEditMode();
|
||||
void DisableEditMode() { IsInEditMode(false); }
|
||||
void AttemptAcceptChanges();
|
||||
void AttemptAcceptChanges(const Control::KeyChord newKeys);
|
||||
void CancelChanges();
|
||||
void DeleteKeyBinding() { _DeleteKeyBindingRequestedHandlers(*this, _CurrentKeys); }
|
||||
|
||||
// ProposedAction: the entry selected by the combo box; may disagree with the settings model.
|
||||
// CurrentAction: the combo box item that maps to the settings model value.
|
||||
// AvailableActions: the list of options in the combo box; both actions above must be in this list.
|
||||
// NOTE: ProposedAction and CurrentAction may disagree mainly due to the "edit mode" system in place.
|
||||
// Current Action serves as...
|
||||
// 1 - a record of what to set ProposedAction to on a cancellation
|
||||
// 2 - a form of translation between ProposedAction and the settings model
|
||||
// We would also need an ActionMap reference to remove this, but this is a better separation
|
||||
// of responsibilities.
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedAction);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentAction);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<hstring>, AvailableActions, nullptr);
|
||||
|
||||
// ProposedKeys: the keys proposed by the control; may disagree with the settings model.
|
||||
// CurrentKeys: the key chord bound in the settings model.
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, CurrentKeys, nullptr);
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsNewlyAdded, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsAutomationPeerAttached, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsHovered, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsContainerFocused, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsEditButtonFocused, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Media::Brush, ContainerBackground, nullptr);
|
||||
TYPED_EVENT(ModifyKeyBindingRequested, Editor::KeyBindingViewModel, Editor::ModifyKeyBindingEventArgs);
|
||||
TYPED_EVENT(DeleteKeyBindingRequested, Editor::KeyBindingViewModel, Terminal::Control::KeyChord);
|
||||
TYPED_EVENT(DeleteNewlyAddedKeyBinding, Editor::KeyBindingViewModel, IInspectable);
|
||||
|
||||
private:
|
||||
hstring _KeyChordText{};
|
||||
};
|
||||
|
||||
struct ActionsPageNavigationState : ActionsPageNavigationStateT<ActionsPageNavigationState>
|
||||
{
|
||||
public:
|
||||
ActionsPageNavigationState(const Model::CascadiaSettings& settings) :
|
||||
_Settings{ settings } {}
|
||||
|
||||
WINRT_PROPERTY(Model::CascadiaSettings, Settings, nullptr)
|
||||
};
|
||||
|
||||
struct Actions : public HasScrollViewer<Actions>, ActionsT<Actions>
|
||||
{
|
||||
public:
|
||||
@@ -114,24 +17,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
||||
|
||||
void AddNew_Click(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
WINRT_PROPERTY(Editor::ActionsPageNavigationState, State, nullptr);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::KeyBindingViewModel>, KeyBindingList);
|
||||
|
||||
private:
|
||||
void _ViewModelPropertyChangedHandler(const Windows::Foundation::IInspectable& senderVM, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
void _ViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& args);
|
||||
void _ViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args);
|
||||
void _ViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& args);
|
||||
|
||||
std::optional<uint32_t> _GetContainerIndexByKeyChord(const Control::KeyChord& keys);
|
||||
void _RegisterEvents(com_ptr<implementation::KeyBindingViewModel>& kbdVM);
|
||||
|
||||
bool _AutomationPeerAttached{ false };
|
||||
Windows::Foundation::Collections::IObservableVector<hstring> _AvailableActionAndArgs;
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionMap;
|
||||
WINRT_OBSERVABLE_PROPERTY(Editor::ActionsViewModel, ViewModel, _PropertyChangedHandlers, nullptr);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +1,13 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "EnumEntry.idl";
|
||||
import "ActionsViewModel.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
runtimeclass ModifyKeyBindingEventArgs
|
||||
{
|
||||
Microsoft.Terminal.Control.KeyChord OldKeys { get; };
|
||||
Microsoft.Terminal.Control.KeyChord NewKeys { get; };
|
||||
String OldActionName { get; };
|
||||
String NewActionName { get; };
|
||||
}
|
||||
|
||||
runtimeclass KeyBindingViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
// Settings Model side
|
||||
String Name { get; };
|
||||
String KeyChordText { get; };
|
||||
|
||||
// UI side
|
||||
Boolean ShowEditButton { get; };
|
||||
Boolean IsInEditMode { get; };
|
||||
Boolean IsNewlyAdded { get; };
|
||||
Microsoft.Terminal.Control.KeyChord ProposedKeys;
|
||||
Object ProposedAction;
|
||||
Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout;
|
||||
String EditButtonName { get; };
|
||||
String CancelButtonName { get; };
|
||||
String AcceptButtonName { get; };
|
||||
String DeleteButtonName { get; };
|
||||
Windows.UI.Xaml.Media.Brush ContainerBackground { get; };
|
||||
|
||||
void EnterHoverMode();
|
||||
void ExitHoverMode();
|
||||
void ActionGotFocus();
|
||||
void ActionLostFocus();
|
||||
void EditButtonGettingFocus();
|
||||
void EditButtonLosingFocus();
|
||||
IObservableVector<String> AvailableActions { get; };
|
||||
void ToggleEditMode();
|
||||
void AttemptAcceptChanges();
|
||||
void CancelChanges();
|
||||
void DeleteKeyBinding();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<KeyBindingViewModel, ModifyKeyBindingEventArgs> ModifyKeyBindingRequested;
|
||||
event Windows.Foundation.TypedEventHandler<KeyBindingViewModel, Microsoft.Terminal.Control.KeyChord> DeleteKeyBindingRequested;
|
||||
}
|
||||
|
||||
runtimeclass ActionsPageNavigationState
|
||||
{
|
||||
Microsoft.Terminal.Settings.Model.CascadiaSettings Settings;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Actions : Windows.UI.Xaml.Controls.Page
|
||||
{
|
||||
Actions();
|
||||
ActionsPageNavigationState State { get; };
|
||||
|
||||
IObservableVector<KeyBindingViewModel> KeyBindingList { get; };
|
||||
ActionsViewModel ViewModel { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
@@ -157,7 +157,6 @@
|
||||
</Style>
|
||||
|
||||
<!-- Converters & Misc. -->
|
||||
<model:IconPathConverter x:Key="IconSourceConverter" />
|
||||
<SolidColorBrush x:Key="ActionContainerBackgroundEditing"
|
||||
Color="{ThemeResource SystemListMediumColor}" />
|
||||
<SolidColorBrush x:Key="ActionContainerBackground"
|
||||
@@ -194,7 +193,7 @@
|
||||
<TextBlock Grid.Column="0"
|
||||
Style="{StaticResource KeyBindingNameTextBlockStyle}"
|
||||
Text="{x:Bind Name, Mode=OneWay}"
|
||||
Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}" />
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}" />
|
||||
|
||||
<!-- Edit Mode: Action Combo-box -->
|
||||
<ComboBox x:Uid="Actions_ActionComboBox"
|
||||
@@ -210,7 +209,7 @@
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">
|
||||
|
||||
<TextBlock FontSize="14"
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
@@ -302,7 +301,7 @@
|
||||
Margin="8,0,0,0"
|
||||
AutomationProperties.Name="{x:Bind DeleteButtonName}"
|
||||
Style="{StaticResource DeleteSmallButtonStyle}"
|
||||
Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(IsNewlyAdded), Mode=OneWay}">
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsNewlyAdded), Mode=OneWay}">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
@@ -347,7 +346,7 @@
|
||||
<!-- Keybindings -->
|
||||
<ListView x:Name="KeyBindingsListView"
|
||||
ItemTemplate="{StaticResource KeyBindingTemplate}"
|
||||
ItemsSource="{x:Bind KeyBindingList, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind ViewModel.KeyBindingList, Mode=OneWay}"
|
||||
SelectionMode="None" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
380
src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp
Normal file
380
src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ActionsViewModel.h"
|
||||
#include "ActionsViewModel.g.cpp"
|
||||
#include "KeyBindingViewModel.g.cpp"
|
||||
#include "LibraryResources.h"
|
||||
#include "../TerminalSettingsModel/AllShortcutActions.h"
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Xaml::Data;
|
||||
using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
KeyBindingViewModel::KeyBindingViewModel(const IObservableVector<hstring>& availableActions) :
|
||||
KeyBindingViewModel(nullptr, availableActions.First().Current(), availableActions) {}
|
||||
|
||||
KeyBindingViewModel::KeyBindingViewModel(const Control::KeyChord& keys, const hstring& actionName, const IObservableVector<hstring>& availableActions) :
|
||||
_CurrentKeys{ keys },
|
||||
_KeyChordText{ KeyChordSerialization::ToString(keys) },
|
||||
_CurrentAction{ actionName },
|
||||
_ProposedAction{ box_value(actionName) },
|
||||
_AvailableActions{ availableActions }
|
||||
{
|
||||
// Add a property changed handler to our own property changed event.
|
||||
// This propagates changes from the settings model to anybody listening to our
|
||||
// unique view model members.
|
||||
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto viewModelProperty{ args.PropertyName() };
|
||||
if (viewModelProperty == L"CurrentKeys")
|
||||
{
|
||||
_KeyChordText = KeyChordSerialization::ToString(_CurrentKeys);
|
||||
_NotifyChanges(L"KeyChordText");
|
||||
}
|
||||
else if (viewModelProperty == L"IsContainerFocused" ||
|
||||
viewModelProperty == L"IsEditButtonFocused" ||
|
||||
viewModelProperty == L"IsHovered" ||
|
||||
viewModelProperty == L"IsAutomationPeerAttached" ||
|
||||
viewModelProperty == L"IsInEditMode")
|
||||
{
|
||||
_NotifyChanges(L"ShowEditButton");
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentAction")
|
||||
{
|
||||
_NotifyChanges(L"Name");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hstring KeyBindingViewModel::EditButtonName() const noexcept { return RS_(L"Actions_EditButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
hstring KeyBindingViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
hstring KeyBindingViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
hstring KeyBindingViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
|
||||
|
||||
bool KeyBindingViewModel::ShowEditButton() const noexcept
|
||||
{
|
||||
return (IsContainerFocused() || IsEditButtonFocused() || IsHovered() || IsAutomationPeerAttached()) && !IsInEditMode();
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::ToggleEditMode()
|
||||
{
|
||||
// toggle edit mode
|
||||
IsInEditMode(!_IsInEditMode);
|
||||
if (_IsInEditMode)
|
||||
{
|
||||
// if we're in edit mode,
|
||||
// - pre-populate the text box with the current keys
|
||||
// - reset the combo box with the current action
|
||||
ProposedKeys(_CurrentKeys);
|
||||
ProposedAction(box_value(_CurrentAction));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::AttemptAcceptChanges()
|
||||
{
|
||||
AttemptAcceptChanges(_ProposedKeys);
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::AttemptAcceptChanges(const Control::KeyChord newKeys)
|
||||
{
|
||||
const auto args{ make_self<ModifyKeyBindingEventArgs>(_CurrentKeys, // OldKeys
|
||||
newKeys, // NewKeys
|
||||
_IsNewlyAdded ? hstring{} : _CurrentAction, // OldAction
|
||||
unbox_value<hstring>(_ProposedAction)) }; // NewAction
|
||||
_ModifyKeyBindingRequestedHandlers(*this, *args);
|
||||
}
|
||||
|
||||
void KeyBindingViewModel::CancelChanges()
|
||||
{
|
||||
if (_IsNewlyAdded)
|
||||
{
|
||||
_DeleteNewlyAddedKeyBindingHandlers(*this, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleEditMode();
|
||||
}
|
||||
}
|
||||
|
||||
ActionsViewModel::ActionsViewModel(Model::CascadiaSettings settings) :
|
||||
_Settings{ settings }
|
||||
{
|
||||
// Populate AvailableActionAndArgs
|
||||
_AvailableActionMap = single_threaded_map<hstring, Model::ActionAndArgs>();
|
||||
std::vector<hstring> availableActionAndArgs;
|
||||
for (const auto& [name, actionAndArgs] : _Settings.ActionMap().AvailableActions())
|
||||
{
|
||||
availableActionAndArgs.push_back(name);
|
||||
_AvailableActionMap.Insert(name, actionAndArgs);
|
||||
}
|
||||
std::sort(begin(availableActionAndArgs), end(availableActionAndArgs));
|
||||
_AvailableActionAndArgs = single_threaded_observable_vector(std::move(availableActionAndArgs));
|
||||
|
||||
// Convert the key bindings from our settings into a view model representation
|
||||
const auto& keyBindingMap{ _Settings.ActionMap().KeyBindings() };
|
||||
std::vector<Editor::KeyBindingViewModel> keyBindingList;
|
||||
keyBindingList.reserve(keyBindingMap.Size());
|
||||
for (const auto& [keys, cmd] : keyBindingMap)
|
||||
{
|
||||
// convert the cmd into a KeyBindingViewModel
|
||||
auto container{ make_self<KeyBindingViewModel>(keys, cmd.Name(), _AvailableActionAndArgs) };
|
||||
_RegisterEvents(container);
|
||||
keyBindingList.push_back(*container);
|
||||
}
|
||||
|
||||
std::sort(begin(keyBindingList), end(keyBindingList), KeyBindingViewModelComparator{});
|
||||
_KeyBindingList = single_threaded_observable_vector(std::move(keyBindingList));
|
||||
}
|
||||
|
||||
void ActionsViewModel::OnAutomationPeerAttached()
|
||||
{
|
||||
_AutomationPeerAttached = true;
|
||||
for (const auto& kbdVM : _KeyBindingList)
|
||||
{
|
||||
// To create a more accessible experience, we want the "edit" buttons to _always_
|
||||
// appear when a screen reader is attached. This ensures that the edit buttons are
|
||||
// accessible via the UIA tree.
|
||||
get_self<KeyBindingViewModel>(kbdVM)->IsAutomationPeerAttached(_AutomationPeerAttached);
|
||||
}
|
||||
}
|
||||
|
||||
void ActionsViewModel::AddNewKeybinding()
|
||||
{
|
||||
// Create the new key binding and register all of the event handlers.
|
||||
auto kbdVM{ make_self<KeyBindingViewModel>(_AvailableActionAndArgs) };
|
||||
_RegisterEvents(kbdVM);
|
||||
kbdVM->DeleteNewlyAddedKeyBinding({ this, &ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler });
|
||||
|
||||
// Manually add the editing background. This needs to be done in Actions not the view model.
|
||||
// We also have to do this manually because it hasn't been added to the list yet.
|
||||
kbdVM->IsInEditMode(true);
|
||||
// Emit an event to let the page know to update the background of this key binding VM
|
||||
_UpdateBackgroundHandlers(*this, *kbdVM);
|
||||
|
||||
// IMPORTANT: do this _after_ setting IsInEditMode. Otherwise, it'll get deleted immediately
|
||||
// by the PropertyChangedHandler below (where we delete any IsNewlyAdded items)
|
||||
kbdVM->IsNewlyAdded(true);
|
||||
_KeyBindingList.InsertAt(0, *kbdVM);
|
||||
_FocusContainerHandlers(*this, *kbdVM);
|
||||
}
|
||||
|
||||
void ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args)
|
||||
{
|
||||
const auto senderVM{ sender.as<Editor::KeyBindingViewModel>() };
|
||||
const auto propertyName{ args.PropertyName() };
|
||||
if (propertyName == L"IsInEditMode")
|
||||
{
|
||||
if (senderVM.IsInEditMode())
|
||||
{
|
||||
// Ensure that...
|
||||
// 1. we move focus to the edit mode controls
|
||||
// 2. any actions that were newly added are removed
|
||||
// 3. this is the only entry that is in edit mode
|
||||
for (int32_t i = _KeyBindingList.Size() - 1; i >= 0; --i)
|
||||
{
|
||||
const auto& kbdVM{ _KeyBindingList.GetAt(i) };
|
||||
if (senderVM == kbdVM)
|
||||
{
|
||||
// This is the view model entry that went into edit mode.
|
||||
// Emit an event to let the page know to move focus to
|
||||
// this VM's container.
|
||||
_FocusContainerHandlers(*this, senderVM);
|
||||
}
|
||||
else if (kbdVM.IsNewlyAdded())
|
||||
{
|
||||
// Remove any actions that were newly added
|
||||
_KeyBindingList.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exit edit mode for all other containers
|
||||
get_self<KeyBindingViewModel>(kbdVM)->DisableEditMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Emit an event to let the page know to move focus to
|
||||
// this VM's container.
|
||||
_FocusContainerHandlers(*this, senderVM);
|
||||
}
|
||||
|
||||
// Emit an event to let the page know to update the background of this key binding VM
|
||||
_UpdateBackgroundHandlers(*this, senderVM);
|
||||
}
|
||||
}
|
||||
|
||||
void ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& keys)
|
||||
{
|
||||
// Update the settings model
|
||||
_Settings.ActionMap().DeleteKeyBinding(keys);
|
||||
|
||||
// Find the current container in our list and remove it.
|
||||
// This is much faster than rebuilding the entire ActionMap.
|
||||
uint32_t index;
|
||||
if (_KeyBindingList.IndexOf(senderVM, index))
|
||||
{
|
||||
_KeyBindingList.RemoveAt(index);
|
||||
|
||||
// Focus the new item at this index
|
||||
if (_KeyBindingList.Size() != 0)
|
||||
{
|
||||
const auto newFocusedIndex{ std::clamp(index, 0u, _KeyBindingList.Size() - 1) };
|
||||
// Emit an event to let the page know to move focus to
|
||||
// this VM's container.
|
||||
_FocusContainerHandlers(*this, winrt::box_value(newFocusedIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args)
|
||||
{
|
||||
const auto isNewAction{ !args.OldKeys() && args.OldActionName().empty() };
|
||||
|
||||
auto applyChangesToSettingsModel = [=]() {
|
||||
// If the key chord was changed,
|
||||
// update the settings model and view model appropriately
|
||||
// NOTE: we still need to update the view model if we're working with a newly added action
|
||||
if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey())
|
||||
{
|
||||
if (!isNewAction)
|
||||
{
|
||||
// update settings model
|
||||
_Settings.ActionMap().RebindKeys(args.OldKeys(), args.NewKeys());
|
||||
}
|
||||
|
||||
// update view model
|
||||
auto senderVMImpl{ get_self<KeyBindingViewModel>(senderVM) };
|
||||
senderVMImpl->CurrentKeys(args.NewKeys());
|
||||
}
|
||||
|
||||
// If the action was changed,
|
||||
// update the settings model and view model appropriately
|
||||
// NOTE: no need to check for "isNewAction" here. <empty_string> != <action name> already.
|
||||
if (args.OldActionName() != args.NewActionName())
|
||||
{
|
||||
// convert the action's name into a view model.
|
||||
const auto& newAction{ _AvailableActionMap.Lookup(args.NewActionName()) };
|
||||
|
||||
// update settings model
|
||||
_Settings.ActionMap().RegisterKeyBinding(args.NewKeys(), newAction);
|
||||
|
||||
// update view model
|
||||
auto senderVMImpl{ get_self<KeyBindingViewModel>(senderVM) };
|
||||
senderVMImpl->CurrentAction(args.NewActionName());
|
||||
senderVMImpl->IsNewlyAdded(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Check for this special case:
|
||||
// we're changing the key chord,
|
||||
// but the new key chord is already in use
|
||||
if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey())
|
||||
{
|
||||
const auto& conflictingCmd{ _Settings.ActionMap().GetActionByKeyChord(args.NewKeys()) };
|
||||
if (conflictingCmd)
|
||||
{
|
||||
// We're about to overwrite another key chord.
|
||||
// Display a confirmation dialog.
|
||||
TextBlock errorMessageTB{};
|
||||
errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage"));
|
||||
|
||||
const auto conflictingCmdName{ conflictingCmd.Name() };
|
||||
TextBlock conflictingCommandNameTB{};
|
||||
conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName));
|
||||
conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic);
|
||||
|
||||
TextBlock confirmationQuestionTB{};
|
||||
confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion"));
|
||||
|
||||
Button acceptBTN{};
|
||||
acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton")));
|
||||
acceptBTN.Click([=](auto&, auto&) {
|
||||
// remove conflicting key binding from list view
|
||||
const auto containerIndex{ _GetContainerIndexByKeyChord(args.NewKeys()) };
|
||||
_KeyBindingList.RemoveAt(*containerIndex);
|
||||
|
||||
// remove flyout
|
||||
senderVM.AcceptChangesFlyout().Hide();
|
||||
senderVM.AcceptChangesFlyout(nullptr);
|
||||
|
||||
// update settings model and view model
|
||||
applyChangesToSettingsModel();
|
||||
senderVM.ToggleEditMode();
|
||||
});
|
||||
|
||||
StackPanel flyoutStack{};
|
||||
flyoutStack.Children().Append(errorMessageTB);
|
||||
flyoutStack.Children().Append(conflictingCommandNameTB);
|
||||
flyoutStack.Children().Append(confirmationQuestionTB);
|
||||
flyoutStack.Children().Append(acceptBTN);
|
||||
|
||||
Flyout acceptChangesFlyout{};
|
||||
acceptChangesFlyout.Content(flyoutStack);
|
||||
senderVM.AcceptChangesFlyout(acceptChangesFlyout);
|
||||
}
|
||||
}
|
||||
|
||||
// update settings model and view model
|
||||
applyChangesToSettingsModel();
|
||||
|
||||
// We NEED to toggle the edit mode here,
|
||||
// so that if nothing changed, we still exit
|
||||
// edit mode.
|
||||
senderVM.ToggleEditMode();
|
||||
}
|
||||
|
||||
void ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& /*args*/)
|
||||
{
|
||||
for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i)
|
||||
{
|
||||
const auto& kbdVM{ _KeyBindingList.GetAt(i) };
|
||||
if (kbdVM == senderVM)
|
||||
{
|
||||
_KeyBindingList.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - performs a search on KeyBindingList by key chord.
|
||||
// Arguments:
|
||||
// - keys - the associated key chord of the command we're looking for
|
||||
// Return Value:
|
||||
// - the index of the view model referencing the command. If the command doesn't exist, nullopt
|
||||
std::optional<uint32_t> ActionsViewModel::_GetContainerIndexByKeyChord(const Control::KeyChord& keys)
|
||||
{
|
||||
for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i)
|
||||
{
|
||||
const auto kbdVM{ get_self<KeyBindingViewModel>(_KeyBindingList.GetAt(i)) };
|
||||
const auto& otherKeys{ kbdVM->CurrentKeys() };
|
||||
if (otherKeys && keys.Modifiers() == otherKeys.Modifiers() && keys.Vkey() == otherKeys.Vkey())
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO GH #6900:
|
||||
// an expedited search can be done if we use cmd.Name()
|
||||
// to quickly search through the sorted list.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ActionsViewModel::_RegisterEvents(com_ptr<KeyBindingViewModel>& kbdVM)
|
||||
{
|
||||
kbdVM->PropertyChanged({ this, &ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler });
|
||||
kbdVM->DeleteKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler });
|
||||
kbdVM->ModifyKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler });
|
||||
kbdVM->IsAutomationPeerAttached(_AutomationPeerAttached);
|
||||
}
|
||||
}
|
||||
131
src/cascadia/TerminalSettingsEditor/ActionsViewModel.h
Normal file
131
src/cascadia/TerminalSettingsEditor/ActionsViewModel.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ActionsViewModel.g.h"
|
||||
#include "KeyBindingViewModel.g.h"
|
||||
#include "ModifyKeyBindingEventArgs.g.h"
|
||||
#include "Utils.h"
|
||||
#include "ViewModelHelpers.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct KeyBindingViewModelComparator
|
||||
{
|
||||
bool operator()(const Editor::KeyBindingViewModel& lhs, const Editor::KeyBindingViewModel& rhs) const
|
||||
{
|
||||
return lhs.Name() < rhs.Name();
|
||||
}
|
||||
};
|
||||
|
||||
struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT<ModifyKeyBindingEventArgs>
|
||||
{
|
||||
public:
|
||||
ModifyKeyBindingEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys, const hstring oldActionName, const hstring newActionName) :
|
||||
_OldKeys{ oldKeys },
|
||||
_NewKeys{ newKeys },
|
||||
_OldActionName{ std::move(oldActionName) },
|
||||
_NewActionName{ std::move(newActionName) } {}
|
||||
|
||||
WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr);
|
||||
WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr);
|
||||
WINRT_PROPERTY(hstring, OldActionName);
|
||||
WINRT_PROPERTY(hstring, NewActionName);
|
||||
};
|
||||
|
||||
struct KeyBindingViewModel : KeyBindingViewModelT<KeyBindingViewModel>, ViewModelHelper<KeyBindingViewModel>
|
||||
{
|
||||
public:
|
||||
KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector<hstring>& availableActions);
|
||||
KeyBindingViewModel(const Control::KeyChord& keys, const hstring& name, const Windows::Foundation::Collections::IObservableVector<hstring>& availableActions);
|
||||
|
||||
hstring Name() const { return _CurrentAction; }
|
||||
hstring KeyChordText() const { return _KeyChordText; }
|
||||
|
||||
// UIA Text
|
||||
hstring EditButtonName() const noexcept;
|
||||
hstring CancelButtonName() const noexcept;
|
||||
hstring AcceptButtonName() const noexcept;
|
||||
hstring DeleteButtonName() const noexcept;
|
||||
|
||||
void EnterHoverMode() { IsHovered(true); };
|
||||
void ExitHoverMode() { IsHovered(false); };
|
||||
void ActionGotFocus() { IsContainerFocused(true); };
|
||||
void ActionLostFocus() { IsContainerFocused(false); };
|
||||
void EditButtonGettingFocus() { IsEditButtonFocused(true); };
|
||||
void EditButtonLosingFocus() { IsEditButtonFocused(false); };
|
||||
bool ShowEditButton() const noexcept;
|
||||
void ToggleEditMode();
|
||||
void DisableEditMode() { IsInEditMode(false); }
|
||||
void AttemptAcceptChanges();
|
||||
void AttemptAcceptChanges(const Control::KeyChord newKeys);
|
||||
void CancelChanges();
|
||||
void DeleteKeyBinding() { _DeleteKeyBindingRequestedHandlers(*this, _CurrentKeys); }
|
||||
|
||||
// ProposedAction: the entry selected by the combo box; may disagree with the settings model.
|
||||
// CurrentAction: the combo box item that maps to the settings model value.
|
||||
// AvailableActions: the list of options in the combo box; both actions above must be in this list.
|
||||
// NOTE: ProposedAction and CurrentAction may disagree mainly due to the "edit mode" system in place.
|
||||
// Current Action serves as...
|
||||
// 1 - a record of what to set ProposedAction to on a cancellation
|
||||
// 2 - a form of translation between ProposedAction and the settings model
|
||||
// We would also need an ActionMap reference to remove this, but this is a better separation
|
||||
// of responsibilities.
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedAction);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentAction);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<hstring>, AvailableActions, nullptr);
|
||||
|
||||
// ProposedKeys: the keys proposed by the control; may disagree with the settings model.
|
||||
// CurrentKeys: the key chord bound in the settings model.
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, CurrentKeys, nullptr);
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsNewlyAdded, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsAutomationPeerAttached, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsHovered, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsContainerFocused, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsEditButtonFocused, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Media::Brush, ContainerBackground, nullptr);
|
||||
TYPED_EVENT(ModifyKeyBindingRequested, Editor::KeyBindingViewModel, Editor::ModifyKeyBindingEventArgs);
|
||||
TYPED_EVENT(DeleteKeyBindingRequested, Editor::KeyBindingViewModel, Terminal::Control::KeyChord);
|
||||
TYPED_EVENT(DeleteNewlyAddedKeyBinding, Editor::KeyBindingViewModel, IInspectable);
|
||||
|
||||
private:
|
||||
hstring _KeyChordText{};
|
||||
};
|
||||
|
||||
struct ActionsViewModel : ActionsViewModelT<ActionsViewModel>, ViewModelHelper<ActionsViewModel>
|
||||
{
|
||||
public:
|
||||
ActionsViewModel(Model::CascadiaSettings settings);
|
||||
|
||||
void OnAutomationPeerAttached();
|
||||
void AddNewKeybinding();
|
||||
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::KeyBindingViewModel>, KeyBindingList);
|
||||
TYPED_EVENT(FocusContainer, IInspectable, IInspectable);
|
||||
TYPED_EVENT(UpdateBackground, IInspectable, IInspectable);
|
||||
|
||||
private:
|
||||
bool _AutomationPeerAttached{ false };
|
||||
Model::CascadiaSettings _Settings;
|
||||
Windows::Foundation::Collections::IObservableVector<hstring> _AvailableActionAndArgs;
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionMap;
|
||||
|
||||
std::optional<uint32_t> _GetContainerIndexByKeyChord(const Control::KeyChord& keys);
|
||||
void _RegisterEvents(com_ptr<implementation::KeyBindingViewModel>& kbdVM);
|
||||
|
||||
void _KeyBindingViewModelPropertyChangedHandler(const Windows::Foundation::IInspectable& senderVM, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
void _KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& args);
|
||||
void _KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args);
|
||||
void _KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& args);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ActionsViewModel);
|
||||
}
|
||||
60
src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl
Normal file
60
src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
runtimeclass ModifyKeyBindingEventArgs
|
||||
{
|
||||
Microsoft.Terminal.Control.KeyChord OldKeys { get; };
|
||||
Microsoft.Terminal.Control.KeyChord NewKeys { get; };
|
||||
String OldActionName { get; };
|
||||
String NewActionName { get; };
|
||||
}
|
||||
|
||||
runtimeclass KeyBindingViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
// Settings Model side
|
||||
String Name { get; };
|
||||
String KeyChordText { get; };
|
||||
|
||||
// UI side
|
||||
Boolean ShowEditButton { get; };
|
||||
Boolean IsInEditMode { get; };
|
||||
Boolean IsNewlyAdded { get; };
|
||||
Microsoft.Terminal.Control.KeyChord ProposedKeys;
|
||||
Object ProposedAction;
|
||||
Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout;
|
||||
String EditButtonName { get; };
|
||||
String CancelButtonName { get; };
|
||||
String AcceptButtonName { get; };
|
||||
String DeleteButtonName { get; };
|
||||
Windows.UI.Xaml.Media.Brush ContainerBackground { get; };
|
||||
|
||||
void EnterHoverMode();
|
||||
void ExitHoverMode();
|
||||
void ActionGotFocus();
|
||||
void ActionLostFocus();
|
||||
void EditButtonGettingFocus();
|
||||
void EditButtonLosingFocus();
|
||||
IObservableVector<String> AvailableActions { get; };
|
||||
void ToggleEditMode();
|
||||
void AttemptAcceptChanges();
|
||||
void CancelChanges();
|
||||
void DeleteKeyBinding();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<KeyBindingViewModel, ModifyKeyBindingEventArgs> ModifyKeyBindingRequested;
|
||||
event Windows.Foundation.TypedEventHandler<KeyBindingViewModel, Microsoft.Terminal.Control.KeyChord> DeleteKeyBindingRequested;
|
||||
}
|
||||
|
||||
runtimeclass ActionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
ActionsViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
|
||||
void OnAutomationPeerAttached();
|
||||
void AddNewKeybinding();
|
||||
|
||||
IObservableVector<KeyBindingViewModel> KeyBindingList { get; };
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> FocusContainer;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> UpdateBackground;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -17,7 +18,6 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="CommonResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<model:IconPathConverter x:Key="IconSourceConverter" />
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind Icon, Converter={StaticResource IconSourceConverter}}" />
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon), Mode=OneTime}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind Name}" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user